Overview

Enabling strict on an existing codebase surfaces errors that were silently passing. Enable one flag at a time and fix by error code so the work stays reviewable. The flag reference is in typescript-strict-mode; every tsconfig option is in typescript-tsconfig. Starting with TypeScript 6.0 (March 2026), strict defaults to true, so upgrading the compiler without setting it explicitly can surface these errors on its own; TypeScript 7.0 (July 2026) is the native-compiler release.

Prerequisites

  • npx tsc --version works. Any version works; on 6.0 and later the steps apply to projects that set "strict": false, or that inherit it from an older config.
  • The build and tests pass before you start.

Steps

1. Measure the baseline

npx tsc --noEmit 2>&1 | tee tsc-errors.txt
grep -o 'error TS[0-9]*' tsc-errors.txt | sort | uniq -c | sort -rn | head

2. Enable flags in this order

Set "strict": false and turn the family on one flag at a time, fixing each batch before the next:

  1. noImplicitAny: add explicit parameter and return types. Use unknown plus narrowing when the type is genuinely open (typescript-narrowing); avoid any.
  2. strictNullChecks: guard instead of asserting.
  3. strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, useUnknownInCatchVariables, alwaysStrict.
  4. Replace the individual flags with "strict": true.
const user = getUser();
if (!user) throw new Error("User not found");   // a guard, not user!.name
console.log(user.name);

Use the non-null assertion ! only when you know something the compiler cannot, and comment why.

3. Fix the common codes

  • TS7006, TS7031: implicit any parameter or binding. Annotate it.
  • TS2532, TS18048: possibly undefined. Guard or default it.
  • TS2564: property not initialized. Initialize it in the constructor; use ! only when a framework assigns it.
  • TS18046: catch (e) is unknown. Narrow with e instanceof Error.

4. Add noUncheckedIndexedAccess

It is not part of strict, and it makes items[0] typed Item | undefined, which catches real out-of-range crashes.

{ "compilerOptions": { "strict": true, "noUncheckedIndexedAccess": true } }
const first = items[0];
if (first === undefined) return;

5. Gate CI

Add npx tsc --noEmit to the pipeline so strictness cannot regress.

Verify it worked

npx tsc --noEmit && echo clean
npx tsc --noEmit 2>&1 | grep -c "error TS"    # 0
npm test

Common errors

  • Hundreds of errors at once. Enable flags singly, as in step 2.
  • A library has no types. Install @types/<package>, or declare the module; avoid skipLibCheck as a fix for your own errors.
  • ! spreads across the code. Each one hides a nullable path; replace them with guards or restructure so null cannot occur.
  • strictPropertyInitialization breaks a DI framework that assigns properties after construction. Use ! on those properties, with a comment.
  • Behavior changes after narrowing. Review code that narrows with typeof, instanceof, or equality checks.

FAQ

Is strict mode on by default in TypeScript?

Yes, starting with TypeScript 6.0 (March 2026); upgrading the compiler without setting strict explicitly can surface strict errors on its own.

How do I turn on strict mode without hundreds of errors at once?

Set "strict": false and enable the family one flag at a time, starting with noImplicitAny and then strictNullChecks, fixing each batch before the next.

Does strict include noUncheckedIndexedAccess?

No. Add it separately; it types items[0] as Item | undefined, which catches real out-of-range crashes.

When is the non-null assertion operator acceptable?

Only when you know something the compiler cannot, such as a DI framework assigning a property after construction, and with a comment saying why.

How do I stop strictness from regressing?

Add npx tsc --noEmit to the CI pipeline.