TypeScript Strict Mode
Master TypeScript strict mode flags to eliminate null exceptions, untyped variables, and unsafe runtime method invocations.
TL;DR
- Enable
strictmode in compiler options for complete type safety. - Prevent runtime null pointer crashes using strict
strictNullChecksvalidation. - Migrate legacy codebases incrementally by enabling individual
compilerOptionsflags.
Strict Mode Flags
Enable all strict flagsTurn on complete strict compiler checking.
{
"compilerOptions": {
"strict": true
}
}Individual flag migrationActivate specific strict checks progressively.
{
"compilerOptions": {
"strict": false,
"noImplicitAny": true,
"strictNullChecks": true
}
}Prevent emit on errorsBlock build output if type errors exist.
{
"compilerOptions": {
"strict": true,
"noEmitOnError": true
}
}Resolving Implicit Any
Annotate function paramsSupply explicit parameter and return types.
// Error under noImplicitAny:
// function add(a, b) { return a + b; }
function add(a: number, b: number): number {
return a + b;
}Typed event listenersAnnotate callback event arguments.
const btn = document.querySelector("button");
btn?.addEventListener("click", (e: MouseEvent) => {
console.log(e.clientX);
});Typed JSON parsingType parsed objects via unknown narrowing.
const raw = '{"id": 1, "name": "Ada"}';
const data: unknown = JSON.parse(raw);
if (typeof data === "object" && data !== null) {
console.log("Valid parsed object");
}Generic array typingProvide explicit element types for empty arrays.
// Avoid: const items = [];
const items: string[] = [];
items.push("alpha");Strict Null Safety
Explicit nullable typesInclude null in variable union declarations.
// Error under strictNullChecks:
// const user: string = null;
const user: string | null = null;Optional chaining accessSafely navigate nested nullable structures.
type Profile = { address?: { city?: string } };
function getCity(p?: Profile): string {
return p?.address?.city ?? "Unknown";
}Type guard narrowingCheck existence before invoking methods.
function format(val: string | null): string {
if (val === null) {
return "N/A";
}
return val.toUpperCase();
}Array find safetyHandle undefined results from array lookups.
const nums = [10, 20, 30];
const found = nums.find(n => n > 15);
if (found !== undefined) {
console.log(found * 2);
}Class Property Initialization
Constructor assignmentAssign properties directly in constructors.
class Account {
id: string;
constructor(id: string) {
this.id = id;
}
}Inline default assignmentProvide initial values directly at declaration.
class RetryPolicy {
maxAttempts: number = 3;
delayMs: number = 1000;
}Parameter propertiesDeclare and initialize members in constructor.
class UserSession {
constructor(
public readonly token: string,
public expiresAt: number
) {}
}Definite assignment assertionMark fields initialized by DI frameworks.
class ViewComponent {
// Initialized by framework lifecycle:
container!: HTMLElement;
}Strict Function and Method Rules
Strict function typesEnsure contravariant function parameters.
type Handler = (e: MouseEvent) => void;
// Error: assigning broader event callbacks
// is disallowed under strictFunctionTypesNo implicit thisDisallow unqualified this references.
interface Counter {
count: number;
increment(this: Counter): void;
}Strict bind call applyType check function invocation arguments.
function greet(g: string, n: string): void {
console.log(`${g}, ${n}`);
}
greet.call(undefined, "Hello", "Ada");No unchecked index accessTreat dictionary lookup results as undefined.
type Scores = Record<string, number>;
const table: Scores = { math: 95 };
const score: number | undefined = table["sci"];Tips
- Enable
strictNullChecksfirst during gradual migrations, as it resolves the vast majority of silent JavaScript runtime bugs. - Use
unknownrather than disablingnoImplicitAnywhen dealing with uncertain dynamic API payloads to preserve rigorous type narrowing.
Warnings
- Never bypass
strictPropertyInitializationwith non-null assertion operators unless external dependency injection or lifecycle hooks guarantee immediate value population. - Toggling
strictmode in large existing projects without progressive milestones can cause hundreds of immediate compile failures across modules.
In Practice
Refactoring legacy un-typed code to satisfy strict compiler checks using defensive narrowing and explicit types.
- Define explicit interfaces with nullable and optional properties.
- Replace implicit any parameters with validated unknown payloads.
- Guard property lookups with narrowing conditions.
- Safely return structured results without null reference exceptions.
interface Profile {
id: string;
name: string;
email?: string;
}
function parseUser(raw: unknown): Profile {
if (!raw || typeof raw !== "object") {
throw new TypeError("Object required");
}
const obj = raw as Record<string, unknown>;
if (typeof obj.id !== "string") {
throw new TypeError("Invalid ID");
}
return {
id: obj.id,
name: String(obj.name ?? "Guest")
};
}FAQ
The strict: true flag enables a collection of compiler checks including strictNullChecks, noImplicitAny, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, and useUnknownInCatchVariables. It provides maximal safety without manually toggling each flag.
Resolve nullable diagnostics by guarding property access with optional chaining obj?.property, nullish coalescing value ?? fallback, or explicit conditional checks like if (obj !== null). These narrowing guards assure the compiler that values exist before access.
Start with strict: false and toggle individual flags sequentially. Begin with noImplicitThis and strictBindCallApply, advance to noImplicitAny, and finally adopt strictNullChecks. Alternatively, enforce strict checks on new directories using TypeScript project references.
The strictPropertyInitialization flag ensures every declared class property is either given an inline default value or definitely assigned inside the constructor body. This eliminates undefined state errors when instantiating classes.
When noImplicitAny is active, TypeScript flags any expression where it cannot infer a type and would otherwise default to any. Replacing unannotated types with unknown satisfies the compiler while forcing developers to narrow types safely before use.