TypeScript Strict Mode Is Worth It (And How to Turn It On)

A plain-English case for TypeScript strict mode — what each strict flag actually catches, the honest objections from teams that turn it off, and how to migrate an existing codebase without grinding to a halt.

"Should we turn on TypeScript strict mode?" is a question that comes up on almost every Angular, React, or Node project I join. The answer is almost always yes, but the reasoning behind it is what determines whether the team actually keeps it on six months later.

Here's the plain-English case for strict mode, what it actually catches, and the few settings I leave off on purpose.

What "strict" actually means

TypeScript's strict: true flag turns on seven smaller checks. The default tsconfig.json ships with strict: false, which means most projects are running with these off even though they think they're using TypeScript.

Here's what each one does, in order of how much pain it prevents:

| Flag | What it catches | | --- | --- | | noImplicitAny | Forgetting to type a parameter — the most common type hole | | strictNullChecks | Treating null and undefined as the same thing as a real value | | strictFunctionTypes | Function-type mismatches in callbacks and event handlers | | strictBindCallApply | Wrong this in bind, call, apply | | strictPropertyInitialization | Class fields that were never assigned | | noImplicitThis | When this has the wrong type | | alwaysStrict | Emits "use strict" (almost always on already) |

You don't have to turn them on one at a time. strict: true flips all seven. That's the move.

For reference, Angular has shipped strict: true in its starter since v12. ng new my-app writes the strict config for you. If you started an Angular project recently and your build is failing on a null check, that's strict Null Checks doing its job.

The thing strict mode actually catches

Most bugs in a typical web app are not "I wrote the wrong algorithm." They're one of these:

  1. A value is undefined and someone tried to use it. A field that wasn't filled in. A promise that resolved without the expected key. An optional config object. Strict null checks forces you to handle that case or the code won't compile.
  2. An object isn't the shape you thought it was. API response missing a field. User input that doesn't match. Strict mode pushes you to narrow the type before you read it.
  3. A function got called with the wrong arguments. Strict function types catches the case where a callback expects (event: MouseEvent) and you're passing (event: Event). Almost silent at runtime.

These three cover most of the bugs I see in production. Strict mode catches them at compile time, which means you fix them once and they're fixed forever — no test needed.

The honest objections

I've heard every reason not to turn it on. Here's how they shake out in practice.

"It will block us from upgrading the codebase." Strict mode is a per-file opt-in: you can flip it on file by file using // @ts-strict or by splitting the tsconfig and migrating gradually. Angular's defaults are already strict, so green-field projects don't have this problem.

"We have so many warnings already." That's the codebase telling you it has bugs. Suppressing them with // @ts-ignore or any is how you end up with the next batch. Fix the warnings — most of them are real type holes.

"Our team is new to TypeScript." Strict mode is the friendliest version of TypeScript for new teams. It catches mistakes that would otherwise show up as runtime errors in the browser. Turning it off doesn't make learning easier; it makes debugging harder.

"It slows us down." For a week. Strict mode adds maybe 15% to your typing time for the first few files while you learn the patterns. After that, it's a net win because you spend less time chasing bugs that should never have shipped.

A real before/after

The most common bug I see in a non-strict codebase.

``typescript function getUserName(userId: string) { const user = users.find(u => u.id === userId); return user.name; } ``

What happens if userId doesn't exist? find returns undefined. undefined.name throws at runtime. In strict mode, TypeScript tells you that before the code runs:

``typescript function getUserName(userId: string): string { const user = users.find(u => u.id === userId); if (!user) { return 'Unknown user'; } return user.name; // user is User here, not User | undefined } ``

The fix is one if and an explicit return. The runtime crash is gone. That pattern — "TypeScript makes you handle the missing case before the user sees a broken page" — is what strict mode buys you.

The settings I leave off

Strict mode turns on seven checks. A few related flags are NOT included. These are the ones I consider on a per-project basis:

noUncheckedIndexedAccess — adds | undefined to every array/object index lookup. Excellent for safety, but adds noise on projects with lots of array work. I turn it on for libraries and turn-key apps, leave it off for prototypes.

exactOptionalPropertyTypes — distinguishes between "the property is missing" and "the property is set to undefined." Powerfully correct, very noisy on teams that didn't know about it. I leave it off unless the team specifically wants it.

noImplicitReturns — useful. Catches functions where some branches don't return. Turn it on.

noFallthroughCasesInSwitch — useful. Catches missing breaks in switches. Turn it on.

noPropertyAccessFromIndexSignature — good for library code, noisy on application code. I usually leave it off.

The answer to "should I turn this on too?" is almost always eventually yes. Just not all at once.

How to migrate an existing project

If you have an existing codebase without strict mode:

  1. Turn on noImplicitAny and strictNullChecks first. They catch the most bugs.
  2. Fix the errors file by file, smallest first. Bootstrap, then shared utilities, then components.
  3. Once those are clean, flip strict: true for new files using // @ts-strict at the top.
  4. When everything compiles, run tsc --noEmit with the full strict config. Fix what remains.
  5. Make the strict config the default in CI. New code stays strict; old code becomes strict as it's touched.

For a small project (under ~30 files), this is an afternoon. For a large Angular codebase, it's usually a week. Either way it pays back the moment you stop writing runtime null checks for fun.

Why I push teams toward it

Strict mode doesn't make TypeScript harder. It makes TypeScript honest. The "flexibility" of looser settings is mostly the flexibility to ship runtime bugs that show up in a user's browser at 2 AM.

If you're starting a new project today, default to strict. If you're inheriting a non-strict codebase, migrate in slices — the wins start showing up in the first file you clean. For the bigger Angular setup, pair this with Standalone Components and the signals API for a codebase that's easier to read six months from now than the day you wrote it.

If you'd like a short review of where to start on your codebase, send me a message — I usually spot the highest-leverage three or four settings in fifteen minutes.

Want help shipping this on your own site?

Free 30-minute consultation. No pressure either way.

Start a project