Introduction
The strict: true flag is not a single check — it is an umbrella that enables 9 individual sub-flags. Understanding each one helps you know exactly what protections you are getting and lets you selectively disable specific checks during migration.
Key Concepts
- strict: A master flag that enables all strict sub-flags at once.
- Sub-flags: Individual flags that each enforce a specific type-checking rule.
- Incremental Adoption: You can enable sub-flags one at a time when migrating an existing project to strict mode.
Real World Context
When migrating a large JavaScript codebase to TypeScript, enabling strict: true all at once may produce thousands of errors. Understanding the sub-flags lets you enable them incrementally — start with noImplicitAny, fix those errors, then enable strictNullChecks, and so on.
Deep Dive
The 9 Strict Sub-Flags
As of TypeScript 5.9, strict: true enables these individual flags:
- strictNullChecks —
nullandundefinedare distinct types.let x: string = nullis an error.
typescript// Without strictNullChecks let name: string = null; // OK (dangerous!) // With strictNullChecks let name: string = null; // Error: Type 'null' is not assignable to type 'string' let maybeName: string | null = null; // OK — explicitly nullable
- noImplicitAny — Error when TypeScript cannot infer a type and falls back to
any.
typescript// Error: Parameter 'x' implicitly has an 'any' type function double(x) { return x * 2; } // Fix: annotate the parameter function double(x: number) { return x * 2; }
-
strictFunctionTypes — Function parameter types are checked contravariantly (stricter than the default bivariant check).
-
strictBindCallApply —
bind,call, andapplyare fully type-checked instead of returningany. -
strictPropertyInitialization — Class properties must be initialized in the constructor or have a definite assignment assertion (
!).
typescriptclass User { name: string; // Error: not initialized constructor() {} // Missing this.name = ... }
-
alwaysStrict — Emits
"use strict"at the top of every output file. -
useUnknownInCatchVariables — Catch clause variables are typed as
unknowninstead ofany.
typescripttry { /* ... */ } catch (e) { // e is 'unknown', not 'any' if (e instanceof Error) console.log(e.message); }
-
noImplicitThis — Error when
thishas an implicitanytype. -
strictBuiltinIteratorReturn — Built-in iterators return
{ value: T, done: boolean }with stricter typing for thedonestate.
Incremental Migration Strategy
json{ "compilerOptions": { "strict": false, "noImplicitAny": true, "strictNullChecks": true // Enable more flags as you fix errors } }
Common Pitfalls
- Assuming strict is just one check — Developers sometimes disable strict thinking it is a single feature. It actually controls 9 separate safety features.
- Using
!to silence strictPropertyInitialization — The definite assignment assertion (name!: string) bypasses the check. Use it sparingly and only when you know the property will be set before access.
Best Practices
- Enable strict: true on new projects — There is no reason to start without it. The short-term friction saves long-term debugging.
- Migrate incrementally — For existing projects, enable sub-flags one at a time, fix errors, commit, and repeat.
Summary
strict: trueenables 9 individual type-checking sub-flags.- Key sub-flags include
strictNullChecks,noImplicitAny, anduseUnknownInCatchVariables. - For migration, enable sub-flags incrementally instead of all at once.
- Never use
!(definite assignment) as a blanket fix forstrictPropertyInitialization.
Code Examples
// useUnknownInCatchVariables: catch variable is 'unknown'
try {
JSON.parse("invalid json");
} catch (error) {
// error is 'unknown' — must narrow before using
if (error instanceof SyntaxError) {
console.log(error.message); // Safe: error is SyntaxError
} else {
console.log("Unexpected error", error);
}
}
// Without this flag, error would be 'any' — no type checking at all