Skip to main content
All articles
Engineering··2 min read

Why we enforce TypeScript strict mode on every project

Strict mode catches bugs at compile time that would otherwise reach production. Here is how we configure it and what it prevents.

TA
Tom Andersen
Software Engineer

We have enforced strict: true in every TypeScript project for three years. The initial friction is real. The long-term payoff is undeniable.

What strict mode enables

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

These flags catch:

  • Null reference errors: strictNullChecks forces you to handle null and undefined
  • Implicit any: Every variable must have a type or inferrable type
  • Unchecked indexing: Array/object access returns T | undefined
  • Optional property mutations: exactOptionalPropertyTypes distinguishes missing from undefined

Real bugs caught

The API response bug

// Without strict mode
const user = await fetchUser(id);
console.log(user.name.toUpperCase()); // Runtime error if user is null
 
// With strict mode
const user = await fetchUser(id);
if (user) {
  console.log(user.name.toUpperCase()); // Safe
}

The config merge bug

// Without strict mode
defaultConfig.timeout = customConfig.timeout || defaultConfig.timeout;
// If timeout is 0, it falls back to default. Bug.
 
// With exactOptionalPropertyTypes
if (customConfig.timeout !== undefined) {
  defaultConfig.timeout = customConfig.timeout;
}

The array access bug

// Without noUncheckedIndexedAccess
const first = items[0];
first.id; // Runtime error if array is empty
 
// With the flag
const first = items[0];
if (first) {
  first.id; // Safe
}

The migration path

Migrating an existing codebase to strict mode is painful but systematic:

  1. Enable strict: true in tsconfig
  2. Run tsc --noEmit
  3. Fix errors by file priority (API boundaries first)
  4. Use satisfies operator for type-safe object literals
  5. Add branded types for ID validation

We budget 2-3 days of engineering time per 10,000 lines of code.

The productivity argument

Critics say strict mode slows development. Our data says otherwise:

  • Bug escape rate: Down 60% after strict mode adoption
  • Code review time: Down 25% (fewer defensive checks needed)
  • Refactoring confidence: Up significantly (compiler catches renames and moves)
  • Onboarding time: Down (types document the codebase)

Configuration we use

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "forceConsistentCasingInFileNames": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  }
}

The bottom line

TypeScript strict mode is not a stylistic preference. It is a correctness tool. The time you save not debugging null reference errors in production pays for the initial migration cost within weeks.

TypeScriptStatic AnalysisCode QualityBest Practices