Skip to content
CodeMorph
Learn

Turning on strict mode without drowning in errors

What "strict" actually enables, the order to turn each flag on, and the fix patterns that clear most errors fast.

Intermediate2 min readUpdated 11 October 2026

"strict": true is where TypeScript earns its keep, and where many migrations hit a wall of red. The trick is that strict isn't one switch. It's a bundle of flags you can enable one at a time.

What strict turns on

FlagWhat it catches
alwaysStrictEmits "use strict" and parses in strict mode
noImplicitThisthis with an implicit any type
strictBindCallApplyWrong arguments to bind, call and apply
strictFunctionTypesUnsafe function parameter assignments
noImplicitAnyValues TypeScript can't infer and silently types as any
strictNullChecksUsing a value that might be null or undefined
strictPropertyInitializationClass properties never assigned in the constructor
useUnknownInCatchVariablesTreats catch (e) as unknown instead of any

Count before you commit

Measure each flag's cost without touching your config:

npx tsc --noEmit --noImplicitAny | grep -c "error TS"
npx tsc --noEmit --strictNullChecks | grep -c "error TS"

Now you know which flags are cheap wins and which are projects.

A good order

  1. The cheap ones first: alwaysStrict, noImplicitThis, strictBindCallApply, strictFunctionTypes. Usually a handful of errors.
  2. noImplicitAny. Forces real types at module boundaries, which makes the next step easier.
  3. strictNullChecks. Usually the biggest. Do it with the rest of the code already well typed.
  4. strictPropertyInitialization and useUnknownInCatchVariables to finish.

Then replace the individual flags with "strict": true, so future flags added to the bundle are picked up automatically.

Fix patterns that clear most errors

Narrow instead of asserting:

function total(cart: Cart | undefined) {
  if (!cart) return 0;
  return cart.items.reduce((sum, i) => sum + i.price * i.qty, 0);
}

Optional chaining and defaults:

const city = user.address?.city ?? "Unknown";

Handle unknown in catch blocks:

try {
  await save();
} catch (err) {
  const message = err instanceof Error ? err.message : String(err);
  logger.error(message);
}

Use the non-null assertion sparingly. value! tells the compiler to trust you. It's fine right after a check the compiler can't see, and a code smell anywhere else.

Escape hatches, used honestly

If you must ship before every error is fixed, prefer // @ts-expect-error over // @ts-ignore: it fails once the error is gone, so stale suppressions can't pile up. Count them and drive the number down.

grep -rn "@ts-expect-error" src | wc -l

Migrate in order, again

The same rule that makes conversion work makes strict mode work: fix leaf modules first. Once lib/db.ts is strictly typed, everything that imports it gets better types for free. CodeMorph's Verify checks the whole project against your tsconfig.json, so you can ask Morph for strict mode and migrate straight to that bar.

Next guideTyping an Express API

Let CodeMorph do the heavy lifting.

Download the beta, open a project and see your migration plan in under a minute.