A practical guide to enabling TypeScript strict mode, what breaks when you turn it on, and how to migrate an existing codebase without losing your mind.
If you write TypeScript and have not switched to strict mode yet, this is the nudge to do it.
TypeScript's strict flag in tsconfig.json is not a single setting. It is a shorthand that enables a bundle of checks: strictNullChecks, noImplicitAny, strictFunctionTypes, strictPropertyInitialization, and a few others.
The most impactful one is strictNullChecks. Without it, null and undefined are silently assignable to any type, which is the source of a large percentage of runtime errors in TypeScript codebases that look typed but are not.
{
"compilerOptions": {
"strict": true
}
}
That single line changes how the compiler reasons about every value in your codebase.
Turning on strict mode in an existing project will surface errors immediately. This is not the compiler being pedantic — it is showing you places where the code assumes things that are not guaranteed.
The most common category is unhandled nullability:
// Before strict: compiles fine, crashes at runtime
function getUsername(user: User | null): string {
return user.name; // user could be null
}
// After strict: compiler catches it
function getUsername(user: User | null): string {
return user?.name ?? "Anonymous";
}
The second most common is implicit any. If you have a function parameter with no type annotation, strict mode flags it rather than silently inferring any.
Do not try to fix everything at once. TypeScript supports per-file opt-out with // @ts-nocheck at the top of a file. Use it temporarily on files you have not migrated yet, and work through them incrementally.
A better approach for large codebases is to enable individual strict checks one at a time, starting with noImplicitAny. Fix those errors, commit, then add strictNullChecks. This gives you smaller, reviewable diffs instead of one enormous migration PR.
{
"compilerOptions": {
"noImplicitAny": true,
"strictNullChecks": true
}
}
Reaching for ! (the non-null assertion operator) to silence errors is the most common trap. It compiles, but you have just told the compiler to stop checking — which defeats the point.
// Bad: silences the error without fixing it
const name = user!.name;
// Good: handle the nullable case
const name = user?.name ?? "Unknown";
Casting to any is the other escape hatch to avoid. If you cannot figure out the right type immediately, use unknown instead and narrow it properly.
After migration, strict mode pays off continuously. Refactoring becomes safer — the compiler catches places you forgot to update. Code review gets faster because the types document intent. New developers can read the types and understand function contracts without reading the implementation.
The upfront cost is real. For a large codebase, a proper strict migration can take days of careful work. For a new project, it costs nothing and catches bugs before they ship.
TypeScript without strict mode gives you the syntax of a typed language with fewer of the safety guarantees. If you are going to use TypeScript, use it properly. Enable strict mode at the start of every new project, and plan a migration path for any existing codebase that does not have it.
The compiler errors it surfaces are not obstacles — they are bugs you caught before your users did.
Comments
Sign in to join the discussion.
No comments yet. Be the first to share your thoughts.