cd ../blog
TypeScriptEngineering ProcessBest Practices

Turning On TypeScript Strict Mode in a Live Production Codebase (Without Breaking Everything)

October 14, 20256 min read

We inherited a large TypeScript codebase with strict mode off. Turning it on revealed 400+ errors. Here's how we migrated incrementally, what the errors actually taught us about the code, and why it was worth the three-sprint investment.

The codebase had "strict": false in tsconfig. It had been that way since the project was bootstrapped two years earlier by a team under deadline pressure. By the time I joined as Tech Lead, the project had 80k+ lines of TypeScript that were, technically, not really TypeScript — they were JavaScript with type annotations and a very permissive compiler.

Running tsc --strict revealed 427 errors across 180 files. Here's what we learned and how we fixed it.

What Strict Mode Actually Enables

"strict": true is a shorthand for six compiler flags:

  • strictNullChecks — variables can't be null/undefined unless you say so
  • strictFunctionTypes — function parameter types are checked contravariantly
  • strictBindCallApply — bind/call/apply are properly typed
  • strictPropertyInitialization — class properties must be initialized
  • noImplicitAny — every untyped variable must be explicitly annotated
  • noImplicitThis — this in functions must be typed

In our codebase, 80% of the 427 errors were from strictNullChecks alone. The codebase was riddled with unchecked nullable access.

The Migration Strategy: Per-File Opt-In

Migrating all 427 errors at once was not an option — we had active feature work. The strategy: incremental migration using per-file // @ts-strict comments.

In TypeScript 5.0+, you can enable strict mode per file with a comment directive at the top. This lets you migrate file-by-file without changing the global tsconfig.

We added a lint rule (custom ESLint rule) that:

  1. Blocked any new file from being created without the // @ts-strict directive
  2. Reported a warning for any modified file that didn't have it

This meant the codebase trended toward full strict coverage over time, enforced by CI.

What the Errors Actually Revealed

The most common pattern was this:

// Before strict:
function getUser(id: string) {
  return users.find(u => u.id === id);
  // return type: User | undefined (but this was ignored)
}

// Consumer:
const user = getUser(id);
console.log(user.email); // Runtime crash if user is undefined

We had dozens of these. In most cases the fix was a simple null check. But in about 20% of cases, the undefined return was a real bug — the calling code had an incorrect assumption that the record would always exist. Strict mode didn't just add type annotations; it exposed actual logic errors.

Patterns We Established

Prefer explicit union returns over throwing.

// Instead of:
function findOrThrow(id: string): User {
  const user = users.find(u => u.id === id);
  if (!user) throw new Error("Not found");
  return user;
}

// We standardised on Result types for expected failure:
type Result<T> = { ok: true; data: T } | { ok: false; error: string };

Use satisfies for config objects. The satisfies operator (TS 4.9) lets you validate that an object matches a type while preserving the literal type information. Enormously useful for config files.

Discriminated unions for state. We replaced boolean flag combinations (isLoading, isError, data) with proper discriminated union state types. This eliminated entire categories of impossible state bugs.

Was It Worth It?

Three sprints of incremental migration work, spread across four months. The outcome:

  • Production null reference errors in our error tracker dropped by ~60% in the two months after full migration
  • Code review cycles got shorter — reviewers stopped needing to mentally track nullability
  • Onboarding new engineers to the codebase became faster — the types serve as accurate documentation

The investment was real. But for a codebase that was going to be actively maintained for years, it was unambiguously the right call.

$ ls ../