TypeScript Strict Mode Explained
"strict": true in your tsconfig.json
is a bundle of seven independent flags. This guide explains what each one does, the kind of bug
it catches, and the order we recommend enabling them during a JavaScript-to-TypeScript migration.
noImplicitAny
Forbids parameters and variables from silently being typed as any.
You either annotate them or let inference do the work — never both nothing.
function greet(name) { // name: any
return "Hello " + name.toUppercase();
// typo not caught at compile time
}
function greet(name) {
// TS7006: Parameter 'name' implicitly has an 'any' type.
}
strictNullChecks
The big one. null and undefined
are distinct types — they no longer assign to other types implicitly. Catches the entire class
of "cannot read property X of undefined" runtime errors at compile time.
function findUser(id: number): User | undefined { /* ... */ }
const u = findUser(42);
u.name; // TS2532: Object is possibly 'undefined'.
u?.name; // OK — optional chaining
if (u) u.name; // OK — narrowed by guard
strictFunctionTypes
Checks function parameter compatibility contravariantly. Plain English: a callback that expects a more specific type cannot be passed where a callback that gets a more general type is expected.
type AnimalCb = (a: Animal) => void;
type DogCb = (d: Dog) => void;
declare let dogCb: DogCb;
const animalCb: AnimalCb = dogCb;
// TS2322: Type '(d: Dog) => void' is not assignable to type 'AnimalCb'.
Usually harmless; surprising the first time you see it. The fix is almost always to make the callback accept the more general type.
strictBindCallApply
Function.prototype.bind, .call,
and .apply require correct argument types. Without this flag
they accept anything.
function greet(name: string, salutation: string) { /* ... */ }
greet.call(null, "Ada", 42);
// TS2769: Argument of type 'number' is not assignable to parameter of type 'string'.
strictPropertyInitialization
Class fields must be initialized in the constructor (or marked with the
! definite-assignment assertion). Catches "I forgot to
set this in the constructor" bugs.
class User {
name: string; // TS2564: Property 'name' has no initializer
age!: number; // OK: definite-assignment assertion
role: string = "user"; // OK: default value
constructor() {} // doesn't set name
}
noImplicitThis
Functions whose this is implicit
any error. Annotate this
explicitly or use arrow functions to inherit lexical this.
function handler() {
this.value;
// TS2683: 'this' implicitly has type 'any'.
}
function handler(this: { value: number }) {
this.value; // OK
}
alwaysStrict
Emits "use strict" at the top of every emitted JavaScript
file. ES modules are already strict, so this only changes output if you target non-module
scripts. Safe to leave on.
Recommended enable order during migration
Turning "strict": true on day one of a migration floods
your team with errors. Instead, enable flags one at a time in this order:
noImplicitAny— flushes out untyped parameters.strictNullChecks— the biggest correctness win. Takes the longest.strictBindCallApply— usually a few quick fixes.strictFunctionTypes— rare in practice.noImplicitThis— catches danglingthisin legacy code.strictPropertyInitialization— loudest; enable last after classes settle.alwaysStrict— safe to enable anytime.
Migrating to strict TypeScript?
JavaScriptConverter does the rename. The strict-mode pass after is yours, and this guide is your map.