TypeScript 71 🔷 Strict Mode — All Flags Explained
Strict mode is not a single setting. It is a group of compiler options that tighten TypeScript’s type checking, and each one catches a different class of bug. The strict: true flag enables the core set, but several important options sit outside it. Knowing what each flag does, and what it costs, is the difference between a codebase that catches its own mistakes and one that lets them through to production. This chapter covers every strict flag: what it does, why it matters, what breaks when you enable it, and the order to adopt them.
Key point: strict: true enables noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, and useUnknownInCatchVariables. The options outside the strict umbrella — noUncheckedIndexedAccess, exactOptionalPropertyTypes, noImplicitOverride, noPropertyAccessFromIndexSignature, noImplicitReturns, and noFallthroughCasesInSwitch — catch additional classes of bugs but are not enabled by default. Each flag has a cost in code changes, and the migration should be incremental.
The strict umbrella
The strict: true flag is the foundation. It enables the core set of type-checking rules that most projects should have on from day one.
{
"compilerOptions": {
"strict": true
}
}
This enables noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, and useUnknownInCatchVariables . For new projects, this is the starting point. For existing codebases, enabling it all at once produces hundreds of errors, and the migration should be incremental.
Why the umbrella matters. The core flags work together. strictNullChecks makes null and undefined distinct types; strictPropertyInitialization requires class properties to be initialized; strictFunctionTypes checks function parameters contravariantly. Each one catches a different class of runtime error that JavaScript would let through.
Why you should still declare them explicitly. Even when strict: true is set, declaring the individual flags makes the configuration explicit and ensures they remain enabled if strict is ever toggled off .
noImplicitAny
Prevents TypeScript from silently falling back to the any type when it cannot infer a more specific type.
// Without noImplicitAny — parameter is implicitly 'any'
function processData(data) {
return data.map(item => item.value); // no checking
}
// With noImplicitAny — explicit type required
function processData(data: DataItem[]): number[] {
return data.map(item => item.value);
}
The flag forces you to either type the variable explicitly or let TypeScript infer a suitable type from usage. The any type disables type checking for everything downstream, so an implicit any is a hole in the type system .
Why it matters. An implicit any lets typos, missing properties, and structural mismatches pass through to runtime. The noImplicitAny flag catches these at compile time .
What breaks. Functions without parameter annotations, useState() calls without type arguments, and event handlers without signatures. The fix is usually a one-line annotation or a type inference from usage.
strictNullChecks
Makes null and undefined distinct types rather than letting them slip into every type.
// Without strictNullChecks — null is assignable to string
let name: string = null; // no error
// With strictNullChecks — forced to handle the null case
let name: string | null = null;
if (name) {
console.log(name.toUpperCase());
}
This is the single most impactful strict flag for runtime safety . Without it, any value could be null at runtime, and TypeScript would not warn you. With it, you must explicitly check for null and undefined before using a value .
Why it matters. Unhandled null is one of the most common sources of runtime errors in JavaScript. A study of TypeScript projects found that approximately 15% of the bugs caught by strictNullChecks were in error-handling or security-relevant code paths .
What breaks. Optional properties, API responses that might be null, and async state. The fix is usually an if check or optional chaining (user?.name).
strictFunctionTypes
Enables contravariant checking for function parameter types.
interface Animal { name: string; }
interface Dog extends Animal { breed: string; }
type AnimalHandler = (animal: Animal) => void;
type DogHandler = (dog: Dog) => void;
const handleAnimal: AnimalHandler = (animal) => console.log(animal.name);
const handleDog: DogHandler = (dog) => console.log(dog.breed);
// Without strictFunctionTypes — allowed (unsafe)
const unsafeHandler: AnimalHandler = handleDog;
// With strictFunctionTypes — error
const safeHandler: AnimalHandler = handleAnimal; // ✅
The flag prevents assigning a function that expects a narrower parameter (like Dog) to a slot that will call it with a wider value (like Animal) .
Why it matters. This catches subtle bugs in callbacks and event handlers where the function signature does not match what the caller will pass .
What breaks. Callback assignments where the parameter types differ. The fix is usually widening the parameter type or adding a runtime check.
strictBindCallApply
Strongly types the bind, call, and apply methods on function objects.
function greet(name: string, age: number): string {
return `Hello ${name}, age ${age}`;
}
// Without strictBindCallApply — accepts anything
greet.call(null, 'Alice', 30); // ✅
greet.call(null, 'Alice'); // ✅ (wrong number of args, no error)
// With strictBindCallApply — checked
greet.call(null, 'Alice', 30); // ✅
// greet.call(null, 'Alice'); // ❌ error: expected 2 arguments
Without this flag, call and apply accept any arguments and return any. With it, the arguments and return type are checked against the function’s signature .
Why it matters. The bind, call, and apply methods are common in callback forwarding and functional composition. The strict checking catches argument mismatches that would fail at runtime.
What breaks. Code that uses apply with arguments objects, which are inherently untyped. The fix is casting to Function or using rest parameters instead .
strictPropertyInitialization
Requires class properties to be initialized in the constructor or with a default value.
class User {
name: string; // ❌ error: not initialized
constructor() {
// name is never assigned
}
}
The flag forces you to either initialize the property in the constructor, assign a default value, or mark it as possibly undefined with | undefined .
Why it matters. Without it, you can declare a property of type string and forget to initialize it, leaving it undefined at runtime despite the type saying otherwise .
What breaks. Classes with dependency injection where properties are assigned after construction. The fix is the definite assignment assertion (!), which tells TypeScript “trust me, this will be assigned” .
useUnknownInCatchVariables
Makes the catch variable unknown instead of any.
try {
risky();
} catch (error) {
// Without the flag: error is any
// With the flag: error is unknown
if (error instanceof Error) {
console.log(error.message); // narrowed
}
}
This flag was added in TypeScript 4.4 and is enabled by strict . The unknown type is more accurate because JavaScript can throw anything — a string, a number, an object.
Why it matters. The any type disables checking on the caught error. With unknown, you must narrow it before accessing properties, which catches the cases where the error is not an Error instance .
What breaks. Code that assumes error.message exists without a check. The fix is an instanceof guard or a custom type guard.
noUncheckedIndexedAccess
Adds undefined to indexed reads on arrays and records.
const arr: string[] = ['a', 'b'];
const first = arr[0]; // string (without the flag)
// const first = arr[0]; // string | undefined (with the flag)
const record: Record<string, string[]> = {};
const tags = record['post-1']; // string[] (without)
// const tags = record['post-1']; // string[] | undefined (with)
By default, TypeScript types arr[i] and record[key] as the element type even when the index is out of range or the key is absent. This flag adds | undefined to indexed reads, forcing a presence check .
Why it matters. This catches crashes that survive every other strict flag. Accessing arr[10] on a 3-element array is undefined at runtime but typed as string without the flag .
What breaks. Every indexed access needs a check or a non-null assertion. The flag is high-impact but high-cost, and it should be adopted after the core strict flags are in place.
exactOptionalPropertyTypes
Distinguishes between an absent property and a property explicitly set to undefined.
interface UserPatch {
nickname?: string;
}
// Without the flag:
const a: UserPatch = { nickname: undefined }; // ✅ allowed
// With the flag:
const b: UserPatch = { nickname: undefined }; // ❌ error
const c: UserPatch = {}; // ✅ allowed
With this flag, an optional property foo?: T means the property may be omitted entirely, but if present, must be exactly T — not T | undefined .
Why it matters. JavaScript distinguishes between a key that is absent and a key set to undefined for in checks, Object.keys, and JSON serialization. This flag preserves that distinction in the type system .
What breaks. Code that explicitly assigns undefined to optional properties. The fix is either omitting the property or modeling the “clear” intent as string | null .
noImplicitOverride
Requires the override keyword when overriding a base class method.
class Base {
greet(): void {}
}
class Derived extends Base {
greet(): void {} // ❌ error: missing 'override'
override greet(): void {} // ✅
}
The flag makes it an error to override a method without the override keyword .
Why it matters. When a base class method is renamed or removed, the subclass method becomes an orphan without the compiler noticing. The override keyword makes the relationship explicit, and the flag ensures it is always used .
What breaks. Subclasses without the override keyword. The fix is mechanical: add override to every method that overrides a base method. Note that implementing abstract methods is not covered by this flag .
noPropertyAccessFromIndexSignature
Forces bracket notation for properties that come from an index signature.
interface Env {
[key: string]: string;
}
const env: Env = { PATH: '/usr/bin' };
const path = env.PATH; // ❌ error: use bracket notation
const path2 = env['PATH']; // ✅
The flag makes it an error to use dot notation to access a property that is only defined via an index signature .
Why it matters. It signals intent: bracket notation tells the reader “this property comes from a dynamic index, and I am confident it exists.” Dot notation implies a declared property .
What breaks. Code that uses dot notation on index signatures. The fix is switching to bracket notation. This flag is not enabled by strict and is not universally recommended .
noImplicitReturns
Reports an error when not all code paths in a function return a value.
function conditional(flag: boolean): string {
if (flag) {
return 'yes';
}
// ❌ error: not all code paths return a value
}
The flag catches functions that return a value on some paths but implicitly return undefined on others .
Why it matters. React components should always return JSX or null. A missing return path produces undefined, which is not valid JSX .
What breaks. Functions with early returns and a missing final return. The fix is adding an explicit return at the end.
noFallthroughCasesInSwitch
Reports an error when a switch case falls through without a break or return.
switch (value) {
case 'a':
doSomething();
// ❌ error: fallthrough
case 'b':
doSomethingElse();
break;
}
The flag catches accidental fallthrough, which is a common source of subtle bugs in switch statements .
Why it matters. Fallthrough is sometimes intentional, but it is more often a mistake. The flag forces you to be explicit: either add a break or use a comment to mark intentional fallthrough.
What breaks. Switch statements with intentional fallthrough. The fix is adding break or restructuring the cases.
Complete Example Session
// tsconfig.json — the full strict configuration
{
"compilerOptions": {
// The core strict umbrella
"strict": true,
// The additional flags outside the umbrella
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
// Explicit declarations of the core flags for clarity
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"useUnknownInCatchVariables": true
}
}
The configuration above represents the full strict setup. For a new project, start with strict: true and add the additional flags incrementally. For an existing project, enable strictNullChecks first (it catches the most bugs), then noImplicitAny, then the rest.
Quick Reference
The strict Umbrella
| Flag | Purpose |
|---|---|
noImplicitAny | No implicit any |
strictNullChecks | null and undefined are distinct |
strictFunctionTypes | Contravariant parameter checking |
strictBindCallApply | Typed bind, call, apply |
strictPropertyInitialization | Class properties must be initialized |
useUnknownInCatchVariables | Catch variable is unknown |
The Additional Flags
| Flag | Purpose |
|---|---|
noUncheckedIndexedAccess | Indexed reads include undefined |
exactOptionalPropertyTypes | Absent vs. undefined distinction |
noImplicitOverride | override keyword required |
noPropertyAccessFromIndexSignature | Bracket notation for index signatures |
noImplicitReturns | All paths must return |
noFallthroughCasesInSwitch | No accidental switch fallthrough |
The Adoption Order
| Priority | Flag | Impact |
|---|---|---|
| 1 | strictNullChecks | Highest |
| 2 | noImplicitAny | High |
| 3 | strict: true | The umbrella |
| 4 | noUncheckedIndexedAccess | High cost |
| 5 | exactOptionalPropertyTypes | Medium |
| 6 | noImplicitOverride | Low cost |
Best Practices
✅ Do This:
// Start with strict: true for new projects
{ "compilerOptions": { "strict": true } } // ✅
// Declare individual flags explicitly for clarity
{ "strict": true, "noImplicitAny": true, "strictNullChecks": true } // ✅
// Add noUncheckedIndexedAccess after the core flags
{ "strict": true, "noUncheckedIndexedAccess": true } // ✅
// Use the override keyword
class Derived extends Base {
override greet(): void {} // ✅
}
// Handle null explicitly with strictNullChecks
if (user) {
console.log(user.name);
} // ✅
❌ Don’t Do This:
// Don't use any to escape strict checks
function process(data: any) { return data.name; } // ⚠️
// Don't enable all flags at once on an existing codebase
{ "strict": true, "noUncheckedIndexedAccess": true } // 400 errors // ⚠️
// Don't forget the null check with strictNullChecks
const name = user.name; // user might be null // ⚠️
// Don't use dot notation on index signatures
const value = env.PATH; // use env['PATH'] // ⚠️
Common Pitfalls
| Pitfall | Problem | Solution |
|---|---|---|
| Enabling all flags at once | Hundreds of errors | Enable incrementally |
strictNullChecks without checks | Runtime crashes | Add null checks |
noUncheckedIndexedAccess everywhere | Verbose code | Use non-null assertions where proven |
Missing override keyword | Orphaned methods | Add override |
Explicit undefined on optional | Type error | Omit the property |
| Catch variable assumed | Runtime error | Guard with instanceof |
Real-World Examples
1. The core strict setup
{ "compilerOptions": { "strict": true } }
2. The null check
if (user) console.log(user.name);
3. The override keyword
override greet(): void {}
4. The indexed access check
const first = arr[0];
if (first !== undefined) console.log(first);
5. The catch guard
catch (error) {
if (error instanceof Error) console.log(error.message);
}
6. The class property initialization
class User {
name = '';
}
7. The definite assignment assertion
class User {
name!: string; // assigned by DI
}
8. The optional property
interface Patch {
nickname?: string; // may be omitted
}
9. The index signature access
const value = env['PATH']; // bracket notation
10. The switch break
case 'a':
doSomething();
break;
Visual: The Strict Flag Groups
┌──────────────────────────────────────────────────────────┐
│ strict: true │
│ ├── noImplicitAny │
│ ├── strictNullChecks │
│ ├── strictFunctionTypes │
│ ├── strictBindCallApply │
│ ├── strictPropertyInitialization │
│ └── useUnknownInCatchVariables │
│ │
├──────────────────────────────────────────────────────────┤
│ THE ADDITIONAL FLAGS │
│ ├── noUncheckedIndexedAccess │
│ ├── exactOptionalPropertyTypes │
│ ├── noImplicitOverride │
│ ├── noPropertyAccessFromIndexSignature │
│ ├── noImplicitReturns │
│ └── noFallthroughCasesInSwitch │
│ │
│ The umbrella is the core. The additional flags are the │
│ refinements. │
│ │
└──────────────────────────────────────────────────────────┘
Visual: The Adoption Order
┌──────────────────────────────────────────────────────────┐
│ 1. strictNullChecks │
│ The highest impact. Catches null crashes. │
│ │
│ 2. noImplicitAny │
│ Closes the any hole. │
│ │
│ 3. strict: true │
│ The umbrella. Enables the rest. │
│ │
│ 4. noUncheckedIndexedAccess │
│ High cost. Add when the core is stable. │
│ │
│ 5. exactOptionalPropertyTypes │
│ Medium cost. For strict optional semantics. │
│ │
│ 6. noImplicitOverride │
│ Low cost. Add anytime. │
│ │
│ The order minimizes the disruption. │
│ │
└──────────────────────────────────────────────────────────┘
Visual: The strictNullChecks Impact
┌──────────────────────────────────────────────────────────┐
│ WITHOUT strictNullChecks │
│ let name: string = null; // ✅ allowed │
│ user.name; // user might be null, no error │
│ │
├──────────────────────────────────────────────────────────┤
│ WITH strictNullChecks │
│ let name: string = null; // ❌ error │
│ let name: string | null = null; // ✅ │
│ if (user) user.name; // ✅ guarded │
│ │
│ The null crashes are caught at compile time. │
│ │
└──────────────────────────────────────────────────────────┘
Summary
| Flag | In strict? | Impact |
|---|---|---|
noImplicitAny | ✅ | High |
strictNullChecks | ✅ | Highest |
strictFunctionTypes | ✅ | Medium |
strictBindCallApply | ✅ | Medium |
strictPropertyInitialization | ✅ | Medium |
useUnknownInCatchVariables | ✅ | Medium |
noUncheckedIndexedAccess | ❌ | High |
exactOptionalPropertyTypes | ❌ | Medium |
noImplicitOverride | ❌ | Low |
noPropertyAccessFromIndexSignature | ❌ | Low |
noImplicitReturns | ❌ | Medium |
noFallthroughCasesInSwitch | ❌ | Low |
Key takeaways:
strict: trueenables the core six flags —noImplicitAny,strictNullChecks,strictFunctionTypes,strictBindCallApply,strictPropertyInitialization, anduseUnknownInCatchVariablesstrictNullChecksis the highest-impact flag — it catches the most runtime bugs, especially in error-handling and security code pathsnoUncheckedIndexedAccessaddsundefinedto array and record access — it catches crashes that survive every other strict flag, but it has a high cost in code changesexactOptionalPropertyTypesseparates absent fromundefined— it preserves the JavaScript distinction between a missing key and a key set toundefinednoImplicitOverriderequires theoverridekeyword — it prevents orphaned methods when a base class is refactored- The adoption should be incremental — start with
strictNullChecks, thennoImplicitAny, then the fullstrictumbrella, then the additional flags - Each flag catches a different class of bug — the type system is layered, and the more layers you enable, the more mistakes it catches
- The cost is real —
noUncheckedIndexedAccessandexactOptionalPropertyTypesrequire significant code changes, and the tradeoff should be deliberate
Remember: Strict mode is not a single setting. It is a spectrum of flags, each catching a different class of bug. Start with the core, add the refinements incrementally, and measure the cost against the benefit. The goal is not maximum strictness — it is the strictness that catches the bugs your code actually has.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!