Why strictNullChecks Should Be Your First TypeScript Flag
TypeScript's strictNullChecks flag turns silent runtime null crashes into loud compile-time errors. Here's what it changes, a worked example, and how to adopt it without drowning in red squiggles.
29 Sept 2025, 13:25 UTC

The Problem You Didn't Know You Had
You write const name: string = getUserName() and TypeScript happily compiles. At runtime, getUserName() returns null because the user hasn't set a display name. Your UI crashes with Cannot read property 'toUpperCase' of null. The compiler never warned you because, by default, TypeScript treats null and undefined as valid values of every type.
This is the hole that strictNullChecks plugs. It's not a new feature—it's been stable since TypeScript 2.0—but many codebases still run without it, either because they started before the flag existed or because enabling it surfaces hundreds of errors at once.
What the Flag Actually Changes
With strictNullChecks: true in your tsconfig.json, the type system stops treating null and undefined as subtypes of everything. A variable declared as string can no longer accept null. If a function might return nothing, its return type must be string | null (or string | undefined).
This forces you to handle the missing case at the call site. The compiler will refuse to compile user.name.toUpperCase() when user.name is string | null until you add a guard, use optional chaining, or explicitly assert non-null.
Worked Example: A User Lookup Function
Consider a simple lookup that returns a user object or null when not found:
// types.ts
interface User {
id: string;
displayName: string | null;
}
function findUser(id: string): User | null {
const record = db.users.find(u => u.id === id);
return record ?? null;
}
Without strictNullChecks, this compiles fine but hides a bug:
// ❌ Compiles without strictNullChecks, crashes at runtime
const user = findUser("abc123");
console.log(user.displayName.toUpperCase()); // TypeError if displayName is null
With the flag enabled, TypeScript errors on the second line: Object is possibly 'null'. You must handle it:
// ✅ Guard with optional chaining
console.log(user?.displayName?.toUpperCase() ?? "Anonymous");
// ✅ Explicit guard
if (user && user.displayName) {
console.log(user.displayName.toUpperCase());
}
// ⚠️ Non-null assertion — use sparingly
console.log(user!.displayName!.toUpperCase());
The first two approaches are safe. The third (!) tells the compiler "trust me, this isn't null" and re-introduces the runtime risk if you're wrong.
Adoption Trade-offs
Enabling strictNullChecks in an existing project is a breaking change for your build. A medium-sized codebase can easily produce 500+ new errors. The flag also increases verbosity—every optional property now requires a check or ?..
The bigger risk is the non-null assertion operator (!). It's a pressure valve: when you're confident a value exists but the compiler can't prove it, value! silences the error. Overusing it defeats the purpose. A lint rule like @typescript-eslint/no-non-null-assertion (set to warn) helps catch habitual misuse.
Incremental Adoption Strategy
- Add the flag to a fresh
tsconfig.jsonin a subdirectory (e.g.,packages/new-service/tsconfig.json) and migrate new code there first. - Run
tsc --noEmit --strictNullCheckson your main codebase to see the error count without failing CI. - Enable
@typescript-eslint/strict-boolean-expressionsto catch loose truthy checks thatstrictNullChecksalone misses. - Fix one module at a time, preferring optional chaining (
?.) and nullish coalescing (??) over!. - Flip the flag globally once the error count hits zero.
You can verify the flag is active by adding a deliberate error:
const test: string = null; // Should error: Type 'null' is not assignable to type 'string'
Run tsc --noEmit—if it complains, strictNullChecks is working. The emitted JavaScript is identical either way; this is purely a compile-time safety net.
Closing Thought
strictNullChecks doesn't eliminate null bugs—it moves them from production to your editor. That's exactly where you want them. Start with new code, migrate incrementally, and treat ! as a code smell, not a convenience.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.