Overview

Enabling strict in an existing TypeScript project surfaces errors that have been silently failing. Fixing them in the right order prevents a flood of 200 errors that paralyzes a team. Enable the flag, triage by error code, and fix the high-payoff errors first. The reference for what each strict flag does lives in typescript-strict-mode; the full tsconfig options reference lives in typescript-tsconfig.

Prerequisites

  • TypeScript 4.9 or newer. Run npx tsc --version to check.
  • A tsconfig.json at the project root. If you only have a jsconfig.json, rename it.
  • The project builds and passes tests before you start. Do not add strict on top of pre-existing failures.

Steps

1. Enable strict in tsconfig.json

strict is a shorthand that enables eight flags at once: noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, alwaysStrict, and useUnknownInCatchVariables.

{
  "compilerOptions": {
    "strict": true,
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext"
  }
}

Run the type-checker immediately:

npx tsc --noEmit 2>&1 | tee tsc-errors.txt
wc -l tsc-errors.txt

2. Triage by error code

Sort errors by code to understand the scope before touching a single file:

grep "error TS" tsc-errors.txt | grep -oP 'TS\d+' | sort | uniq -c | sort -rn | head -20

Fix in this order:

  1. TS2322, TS2345 (type mismatch): usually wrong any types that need replacing.
  2. TS7006, TS7031 (noImplicitAny on parameters): add explicit types.
  3. TS2531, TS2532 (possibly null/undefined): add null checks or non-null assertions where safe.
  4. TS2564 (property not assigned in constructor): use definite assignment (!) only when initialization is guaranteed, or initialize in the constructor.
  5. TS18046 (unknown in catch blocks): replace catch (e: any) with catch (e: unknown) and narrow the type.

3. Fix noImplicitAny first

Implicit any is the root cause of most downstream errors. Replace any with real types wherever possible.

// Before: implicit any on parameter
function process(items) {
  return items.map(i => i.name);
}
 
// After: explicit type
function process(items: Array<{ name: string }>) {
  return items.map(i => i.name);
}

When the type is genuinely unknown at the call site, use unknown and narrow it; avoid any. See typescript-narrowing for narrowing patterns.

4. Fix strictNullChecks errors

strictNullChecks rejects assignments of null | undefined to non-optional types. The fix is almost always to guard before use, not to add !.

// Before: possible null ignored
const user = getUser();
console.log(user.name); // TS2532: Object is possibly undefined
 
// After: explicit guard
const user = getUser();
if (!user) throw new Error("User not found");
console.log(user.name); // safe

Use ! (non-null assertion) only when you have context the compiler does not, and add a comment explaining why.

5. Add noUncheckedIndexedAccess

This flag is not included in strict but closes the most common real-world runtime crash: reading an array index that may be undefined.

{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true
  }
}

After adding it, array accesses return T | undefined. Update loops to guard the index:

const first = items[0]; // now typed as Item | undefined
if (first === undefined) return;
console.log(first.name);

6. Verify the build is clean

npx tsc --noEmit
# No output means zero type errors.
 
npm test
# All tests pass with stricter types.

Verify it worked

# 1. tsc reports zero errors.
npx tsc --noEmit && echo "Clean"
 
# 2. The error count from step 2 is now zero.
npx tsc --noEmit 2>&1 | grep -c "error TS"
# 0
 
# 3. CI passes.
# Push to a branch and confirm the type-check step is green.

Common errors

  • Hundreds of errors after enabling strict. Enable flags one at a time: set strict: false and add "noImplicitAny": true first. Fix that batch, then add "strictNullChecks": true, and so on.
  • Third-party library causes errors. The library lacks type definitions. Install @types/<package> or add "skipLibCheck": true temporarily while you file an upstream issue.
  • Non-null assertion (!) spreads through the codebase. This defeats the purpose of strictNullChecks. Replace each ! with a proper guard or restructure the code so null is not possible.
  • strictPropertyInitialization breaks classes with DI frameworks. Decorators initialize properties outside the constructor. Use the useDefineForClassFields: false option with older decorators, or declare properties as declare to suppress the error.
  • Build passes but tests fail after strict changes. The type guards changed runtime behavior. Review any code that narrows via typeof, instanceof, or explicit checks.