Development

TypeScript Strict Mode Is Worth the Pain — Here's How to Migrate

Enabling strict mode on an existing TypeScript codebase feels brutal at first. But the bugs it catches are real, and the migration is more manageable than it looks.

Milo
3 min read
TypeScript Strict Mode Is Worth the Pain — Here's How to Migrate

I avoided enabling strict: true in tsconfig.json for months. The codebase had about 200 files — mostly typed, but with implicit any scattered everywhere. Flipping the switch produced over 400 errors.

It took two days to fix them. Every single one was a real bug waiting to happen.

What Strict Mode Actually Enables

strict: true is shorthand for a collection of compiler flags. The ones that cause the most friction — and catch the most bugs:

FlagWhat it does
strictNullChecksForces explicit handling of null and undefined
noImplicitAnyBans silent any on function parameters
strictFunctionTypesCatches unsound function parameter variance
strictPropertyInitializationClass properties must be initialized or declared optional
noImplicitThisPrevents this from being implicitly any

Together, they close most of the type holes that make TypeScript feel unreliable. Individually, strictNullChecks alone catches roughly half the bugs in most migrations.

The Migration Strategy That Worked

Don’t flip the switch globally and try to fix 400 errors in one sitting. Go flag by flag:

  1. Start with strictNullChecks. It produces the most errors but catches the most real bugs. Work through one module at a time — start with leaf modules that have fewer dependents.
  2. Use // @ts-expect-error sparingly as a temporary escape hatch. Track these with an ESLint rule like @typescript-eslint/prefer-ts-expect-error so they don’t pile up silently.
  3. Add noImplicitAny next. Usually easier — most parameters already have types. You’re just catching the ones that slipped through.
  4. Enable the rest together. The remaining flags tend to produce far fewer errors once null checks and implicit anys are handled.

If you want even more granularity, TypeScript’s --showConfig flag prints exactly which strict sub-flags are active, so you can verify your incremental progress.

Bugs I Actually Found

These weren’t hypothetical — strict mode surfaced real issues hiding in production code:

  • A silent undefined return. A function returned undefined when the caller expected an object. It only failed when a specific API endpoint returned an empty response — a path that unit tests didn’t cover.
  • A null DOM element. An event handler was typed to always receive an element, but document.querySelector returned null when the component rendered before the target existed.
  • An implicit any swallowing errors. A utility function accepted any implicitly, which meant three different call sites were passing wrong types without any compiler warning.

None of these had caused production incidents yet. All of them would have.

Is It Worth It?

Unequivocally yes. The two days of migration paid for themselves the following week when strict null checks caught a bug during a refactor that would have shipped to production. That pattern kept repeating — every major refactor since has been caught by the type system rather than by users.

If you’re starting a new project, enable strict mode from day one — the cost is near zero when there’s no legacy code to fix. If you’re migrating, do it incrementally with the strategy above. Either way, do it.

Written by

Milo

Developer