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 --versionto check. - A
tsconfig.jsonat the project root. If you only have ajsconfig.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.txt2. 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 -20Fix in this order:
- TS2322, TS2345 (type mismatch): usually wrong
anytypes that need replacing. - TS7006, TS7031 (
noImplicitAnyon parameters): add explicit types. - TS2531, TS2532 (possibly null/undefined): add null checks or non-null assertions where safe.
- TS2564 (property not assigned in constructor): use definite assignment (
!) only when initialization is guaranteed, or initialize in the constructor. - TS18046 (
unknownin catch blocks): replacecatch (e: any)withcatch (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); // safeUse ! (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: setstrict: falseand add"noImplicitAny": truefirst. 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": truetemporarily while you file an upstream issue. - Non-null assertion (
!) spreads through the codebase. This defeats the purpose ofstrictNullChecks. Replace each!with a proper guard or restructure the code so null is not possible. strictPropertyInitializationbreaks classes with DI frameworks. Decorators initialize properties outside the constructor. Use theuseDefineForClassFields: falseoption with older decorators, or declare properties asdeclareto 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.