TypeScript Strict Mode
Understand strict mode benefits, gotchas, and how to enable it progressively.
TL;DR
- 01Enable "strict": true in tsconfig.json for maximum type safety.
- 02Strict mode includes multiple flags that can be enabled individually.
- 03Migrate existing code gradually by enabling flags one at a time.
Tips
- 01Enable strict mode from the start on new projects — fixing types upfront is easier than retrofitting them later.
Warnings
- 01Enabling strict mode on large existing codebases requires significant effort — plan for gradual migration over multiple sprints.
Enabling Strict Mode
- Enable strict mode to enable all strict flags at once.
{ "compilerOptions": { "strict": true } } - Strict mode enables these flags simultaneously.
{ "compilerOptions": { "noImplicitAny": true, "noImplicitThis": true, "strictNullChecks": true, "strictFunctionTypes": true, "strictBindCallApply": true, "strictPropertyInitialization": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true } } - New TypeScript projects should use strict mode from the start.
{ "compilerOptions": { "strict": true, "target": "ES2020", "module": "ESNext" } } - Existing projects can migrate gradually by enabling individual flags.
{ "compilerOptions": { "strict": false, "noImplicitAny": true } } - Use noEmitOnError to prevent emitting code when type errors exist.
{ "compilerOptions": { "strict": true, "noEmitOnError": true } }
Implicit Any Errors
- Require types for variables without explicit type annotations.
// With noImplicitAny: error function add(a, b) { return a + b; } // Fixed: explicit types function add(a: number, b: number): number { return a + b; } - Enable noImplicitAny to catch typing mistakes early.
// Error: parameter 'event' implicitly has an 'any' type document.addEventListener("click", (event) => { }); // Fixed document.addEventListener("click", (event: MouseEvent) => { }); - Annotate callback parameters that TypeScript cannot infer.
// Error: 'item' implicitly has type 'any' const items = JSON.parse(data); items.forEach((item) => console.log(item)); // Fixed: type the parsed value const items: string[] = JSON.parse(data); items.forEach((item: string) => console.log(item)); - Use unknown instead of any when the type is genuinely unknown.
function process(input: unknown) { if (typeof input === "string") { console.log(input.toUpperCase()); // Safe after narrowing } } - Reduces bugs from unintended type mismatches throughout the codebase.
// Without noImplicitAny: silent runtime bug function double(n) { return n * 2; } double("3"); // "33" at runtime — no compile-time error
Null Check Errors
- Treat null and undefined as distinct types.
// With strictNullChecks: error const name: string = null; // Fixed: include null in type const name: string | null = null; - Require null checks before accessing properties.
function getName(user: User | null) { if (!user) return "Guest"; return user.name; // Safe: user is not null here } - Prevents many common runtime errors from unchecked nulls.
// Error: Object is possibly undefined const first = items[0].name; // Fixed const first = items[0]?.name ?? "Unknown"; - Enable strictNullChecks independently before enabling full strict mode.
{ "compilerOptions": { "strictNullChecks": true } } - Most impactful strict flag — catching nullable bugs early saves debugging time.
// Without strictNullChecks: no error, crashes at runtime const user = getUser(); // may return null console.log(user.name); // TypeError: Cannot read properties of null
Property Init Errors
- Require class properties to be initialized in the constructor.
// With strictPropertyInitialization: error class User { name: string; } // Fixed: initialize in constructor class User { name: string; constructor(name: string) { this.name = name; } } - Use definite assignment assertion (!) if initialization is deferred.
class User { name!: string; // tells TypeScript: trust me, this will be set async init() { this.name = await fetchName(); } } - Initialize properties with a default value to satisfy the compiler.
class Config { retries: number = 3; timeout: number = 5000; debug: boolean = false; } - Use readonly with constructor initialization for immutable properties.
class Product { readonly id: string; readonly name: string; constructor(id: string, name: string) { this.id = id; this.name = name; } } - Mark properties that are set outside the constructor with declare if needed.
class Widget { declare element: HTMLElement; // set by framework before use render() { this.element.innerHTML = "Hello"; } }
Progressive Migration
- Disable strict mode initially for large migrations.
{ "compilerOptions": { "strict": false } } - Enable individual flags gradually as code is typed.
{ "compilerOptions": { "strict": false, "noImplicitAny": true } } - Use stricter settings for new code while migrating old code.
{ "compilerOptions": { "strict": false }, "ts-node": { "compilerOptions": { "strict": true } } } - Gradually increase strictness until full strict mode is enabled.
{ "compilerOptions": { "noImplicitAny": true, "strictNullChecks": true, "strictPropertyInitialization": true, "strict": true } } - Use @ts-strict-ignore comments to suppress errors in files not yet migrated.
// @ts-strict-ignore // This file is pending strict migration — tracked in issue #42 export function legacyHelper(data) { return data.value; }
FAQ
It enables a bundle of flags including strictNullChecks, noImplicitAny, strictFunctionTypes, strictPropertyInitialization, and others. You can enable each flag individually if you want finer control during migration.
Use optional chaining (obj?.prop), nullish coalescing (val ?? default), or explicit null checks (if (val !== null)) before accessing the value. Type narrowing with typeof or instanceof also works for more complex cases.
Not directly per-file, but you can use multiple tsconfig.json files — a strict one for new code and a looser one for legacy code — by splitting source directories and using project references.
noImplicitAny is one of several flags that strict mode bundles together; enabling strict: true is equivalent to enabling noImplicitAny plus strictNullChecks, strictFunctionTypes, strictBindCallApply, and strictPropertyInitialization all at once.
strictPropertyInitialization requires that class properties are either initialized in the declaration or definitely assigned in the constructor. Use a definite assignment assertion (prop!: Type) if you know it will be set externally, or initialize it to a default value.