"strict": true enables a bundle of flags. Understanding each one helps you decide which to enable and in what order.
strictNullChecks
null and undefined are not assignable to other types. This is the single most impactful flag.
// Without strictNullChecks
function greet(name: string) {
return `Hello, ${name}`;
}
greet(null); // No error — compiles fine
// With strictNullChecks
function greet(name: string) {
return `Hello, ${name}`;
}
greet(null); // Error: Argument of type 'null' is not assignable to parameter of type 'string'.
Enable this first. It catches the most real-world bugs.
noImplicitAny
When TypeScript cannot infer a type, it normally falls back to any. This flag makes those implicit any types an error:
// Without noImplicitAny
function process(data) {
// data is implicitly `any` — no error
}
// With noImplicitAny
function process(data) {
// Error: Parameter 'data' implicitly has an 'any' type.
}
// Fix: annotate explicitly
function process(data: unknown) {
// ...
}
Enable this early. It forces you to be explicit about uncertain types.
strictFunctionTypes
Function parameter types are checked more strictly, preventing unsafe function assignments:
// Without strictFunctionTypes
function logString(s: string) { /* ... */ }
function logAny(s: any) { /* ... */ }
let fn: (s: string) => void = logString;
fn = logAny; // Allowed unsafely
// With strictFunctionTypes
fn = logAny; // Error — function types are checked contravariantly
strictPropertyInitialization
Class properties must be initialized in the constructor or have a default value:
// Without strictPropertyInitialization
class User {
name: string; // uninitialized — no error
}
// With strictPropertyInitialization
class User {
name: string; // Error: Property 'name' has no initializer
// and is not definitely assigned in the constructor.
}
// Fix: initialize
class User {
name: string = "";
// or: initialize in constructor
}
noImplicitThis
this expressions must have a known type:
// Without noImplicitThis
const btn = {
name: "Submit",
onClick: function() {
console.log(this.name); // this is implicitly any
},
};
// With noImplicitThis
const btn = {
name: "Submit",
onClick: function() {
console.log(this.name); // Error: 'this' implicitly has type 'any'
},
};
alwaysStrict
Emit "use strict" at the top of every output file. This enables JavaScript strict mode at runtime.
useUnknownInCatchVariables
Catch clause variables are typed as unknown instead of any:
// Without useUnknownInCatchVariables
try {
// ...
} catch (error) {
// error is `any` — can access anything
console.log(error.message); // No type error, may crash
}
// With useUnknownInCatchVariables
try {
// ...
} catch (error) {
// error is `unknown` — must narrow
if (error instanceof Error) {
console.log(error.message); // Safe
}
}
Enabling flags incrementally
For migrations, enable one at a time:
{
"compilerOptions": {
"strict": false,
"strictNullChecks": true
}
}
Fix all errors from one flag before enabling the next. The order of impact (highest to lowest):
strictNullChecksnoImplicitAnyuseUnknownInCatchVariablesstrictPropertyInitializationstrictFunctionTypesnoImplicitThisalwaysStrict
What to carry forward
strictNullChecksis the most impactful — enables null safetynoImplicitAnyforces explicit typing where inference failsuseUnknownInCatchVariablesmakes catch blocks safestrictPropertyInitializationprevents uninitialized class properties- enable flags one at a time for migrations
strict: trueis the goal for all new projects
The next lesson covers additional important compiler flags beyond strict mode.