Back to Blog
EngineeringJanuary 28, 20267 min read

Why We Use TypeScript Strict Mode on Every Project

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.