Skip to main content
C
CIPHRX
All articles
Engineering··3 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.

C
CIPHRX
Engineering team

Every TypeScript project we touch runs with strict: true. The initial friction is real. The long-term payoff is hard to argue with.

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

Budget roughly 2-3 days of engineering time per 10,000 lines of code, depending on how much any is in the codebase already.

The productivity argument

Critics say strict mode slows development. In practice:

  • Bugs that reach production drop noticeably — null and undefined errors stop being a class of incident
  • Code review speeds up because reviewers stop chasing defensive checks
  • Refactoring confidence is much higher — the compiler catches renames and moves the moment they break something
  • Onboarding is faster because types are documentation that can't go stale

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