Why We Use TypeScript Strict Mode on Every Project
Most TypeScript codebases start with strict mode off. Teams move fast, ship features, and quietly ignore the red squiggles. Then six months in, a production bug slips through that strict mode would have caught at compile time. Here is what each strict flag actually does and how to turn them on without stopping feature work.
What "Strict Mode" Actually Means
"strict": true in your tsconfig.json is not a single flag. It enables a collection of checks: strictNullChecks, noImplicitAny, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, useUnknownInCatchVariables, and alwaysStrict. Each one catches a different class of bug.
strictNullChecks: The Single Most Valuable Flag
Without this flag, null and undefined are assignable to every type. The compiler will not warn you about this:
function getUser(id: string) {
return db.users.find((u) => u.id === id); // returns User | undefined
}
const user = getUser("abc");
console.log(user.name); // runtime crash if user is undefined
With strictNullChecks enabled, TypeScript forces you to handle the undefined case before accessing .name. This single flag eliminates the most common category of runtime errors in JavaScript. If you only enable one strict flag, make it this one.
noImplicitAny: No More Silent Type Erasure
When TypeScript cannot infer a type, it silently assigns any. With noImplicitAny, the compiler forces you to declare it:
// Without the flag: "items" is silently typed as any[]
function processItems(items) {
return items.map((item) => item.value);
}
// With the flag: you must declare the type
function processItems(items: CartItem[]) {
return items.map((item) => item.value);
}
The danger of implicit any is that it turns off type checking for everything downstream. One untyped function parameter propagates any through your entire call chain.
strictFunctionTypes: Catching Subtle Callback Bugs
This checks function parameters contravariantly (the correct behavior). Without it, you can assign a handler expecting a Dog to a slot expecting an Animal handler, and it compiles fine. Then calling the handler with a plain Animal crashes because the function tries to access dog.breed. This shows up most often in event handlers and callback patterns.
useUnknownInCatchVariables: Safer Error Handling
By default, the error in a catch block is typed as any. With this flag, it becomes unknown:
try {
await fetchData();
} catch (error) {
// With the flag, you must narrow the type first
if (error instanceof Error) {
console.log(error.message);
}
}
Not everything thrown in JavaScript is an Error. Libraries throw strings, numbers, plain objects. Assuming .message exists is a bug waiting to happen.
Migrating an Existing Codebase Incrementally
You have 200,000 lines of TypeScript with strict mode off. Turning it on produces 3,000 errors. You cannot pause feature work for two weeks to fix them all. Here is the strategy that works.
Enable flags one at a time. Do not flip "strict": true all at once. Start with strictNullChecks (catches the most bugs), then noImplicitAny, then the rest.
Use path-based overrides. TypeScript lets you override settings per directory using project references. Migrate module by module:
{
"extends": "./tsconfig.json",
"compilerOptions": { "strictNullChecks": true, "noImplicitAny": true },
"include": ["src/modules/billing/**/*", "src/modules/auth/**/*"]
}
Suppress with ts-expect-error, not ts-ignore. @ts-expect-error will itself error when the underlying issue is fixed, so suppressions do not go stale. You can grep for them and track migration progress.
Require strict mode in new files. Add a CI check that requires all new files to pass strict checking. This prevents the backlog from growing while you work through existing code.
The Cost of Waiting
Every week you run without strict mode is a week where the compiler is not catching null pointer errors, implicit any propagation, and unsafe function assignments. Turning on strict mode is not a refactor. It is enabling the tool you are already paying for to do its actual job.
Related Articles
Choosing the Right Tech Stack for Your Startup in 2026
Speed of development, production scalability, and zero DevOps overhead. We break down how to choose a front-end and back-end stack that lets small teams ship fast without painting themselves into a corner.
Architecting Real-Time Trading UIs: WebSockets, Optimistic Updates, and Latency
When milliseconds matter, every rendering decision counts. We break down the architecture patterns we use to build sub-30ms trading interfaces with WebSocket data feeds.