TypeScript Strict Mode: What Actually Happens When You Turn It On
3 min read
You flip "strict": true in an established codebase, the editor holds its breath, and
the terminal delivers the verdict: 340 errors. This is the moment most strict-mode
migrations die — someone declares it "not worth it right now," the flag gets reverted,
and the codebase stays half-typed forever. Having shepherded this migration on a
production app, I can tell you: the 340 number is misleading, the work is very doable,
and the payoff is one of the best returns in maintenance engineering. Here's the map.
What strict mode actually checks
strict is an umbrella flag. The two that generate ~90% of your errors:
noImplicitAny— TypeScript will no longer silently type things asanywhen it can't infer them. Every unannotated function parameter surfaces. This is TypeScript admitting all the places it had quietly given up on checking your code.strictNullChecks— the big one.nullandundefinedstop being assignable to everything.document.querySelectorreturnsElement | null;users.find(...)returnsUser | undefined; and you must handle the empty case before using the value. This check alone targets the most common runtime crash in JavaScript: "cannot read properties of undefined."
The rest of the family (strictFunctionTypes, strictPropertyInitialization,
strictBindCallApply, and friends) produce comparatively few errors and mostly catch
genuinely wrong code.
The reframe that helps teams: strict mode doesn't create 340 problems — it reveals 340 places where problems were already possible. The bugs were in the building; you just turned the lights on.
Why incremental beats the big-bang PR
The 2,000-line "enable strict mode" PR is a trap: unreviewable, permanently in conflict
with feature work, and rushed at the edges — which matters, because null-check fixes
made carelessly (sprinkling ! everywhere) preserve the crashes while silencing the
compiler. Incremental migration keeps every diff small and reviewed, and it never blocks
the roadmap.
A file-by-file strategy that works
1. Stop the bleeding. Enable strict mode for the whole repo... in CI only for new
files, or more simply: enable it and bulk-add a // @ts-nocheck (or targeted
@ts-expect-error) header to currently-failing files via a script. New code is strict
from day one; the legacy list only shrinks.
2. Burn down the list by traffic, not alphabet. Fix the highest-churn files first — shared utilities, API layers, anything imported everywhere. Fixes there propagate types into dependent files and often fix downstream errors for free. A stale admin screen can stay unchecked for months; nobody's touching it anyway.
3. Fix honestly, not cosmetically. The three tiers of null-handling, best to worst:
actually handle the case (early return, error state); prove it can't happen and encode
that (?? defaults, narrowing via type guards);
and — rarely, with a comment — the non-null assertion !. If a file's fix is fifty
!s, you haven't migrated it; you've disabled it with extra steps.
4. Ratchet in CI. Track the unchecked-file count and fail the build if it grows. Twenty minutes of scripting turns the migration from a project into a gradient — every sprint the number drops a little, and there's no way back.
The payoff, honestly stated
The short-term pain is real: a few weeks of visible burn-down work and some grumbling
about | undefined. The long-term ledger is lopsided. After our migration, the "cannot
read property of undefined" class of production error — previously our most common
crash — essentially disappeared from new code. Refactors got faster and calmer: change
a type, follow the compiler errors, done — no archaeology to find who else relied on the
old shape. And code review improved in a subtle way: reviewers stopped spending
attention on "what if this is null?" and spent it on logic.
Strict mode is TypeScript. Everything short of it is a preview with the safety features switched off — useful for adoption, but not the destination. Turn it on, ratchet the number down, and let the compiler carry the paranoia so your team doesn't have to.