Why TypeScript Strict Mode Saved Our Startup

👨‍💻
Karen M.
Lead EngineerNov 25, 20249 min read
#TypeScript#DX#Quality#Engineering
AI TL;DR (Too Long; Didn't Read)
  • Strict mode caught 847 potential bugs in our 50K line codebase
  • Migration took 3 weeks but prevented an estimated 6 months of bug fixes
  • Start with strictNullChecks, then add other flags incrementally

The Near Disaster

Three days before our Series A demo, our app crashed. The cause? A null reference that TypeScript should have caught—but didn't, because we weren't using strict mode.

typescript
// The bug that almost killed our funding
function getUser(id: string) {
  return users.find(u =

u.id === id);

// Returns User | undefined // But we treated it as User }

// Later in the code

const user = getUser(userId); console.log(user.name); // 💥 Cannot read property 'name' of undefined

"That crash cost us 72 hours of sleep and nearly $2M in funding."

What is Strict Mode?

TypeScript's strict mode enables a set of type-checking flags that catch more errors:

FlagWhat It Does
strictNullChecksnull and undefined are distinct types
strictFunctionTypesStricter function parameter checking
strictBindCallApplyCorrect types for bind, call, apply
strictPropertyInitializationClass properties must be initialized
noImplicitAnyMust explicitly type 'any' usages
noImplicitThis'this' must have explicit type
alwaysStrictEmit 'use strict' in all files
json
// tsconfig.json - The nuclear option
{
  "compilerOptions": {
    "strict": true  // Enables ALL strict flags
  }
}

Our Migration Strategy

Week 1: Assessment
  • Enabled strict mode
  • Counted errors: 847 😱
  • Categorized by type and severity
Week 2: Critical Fixes
  • Fixed null reference issues (423 errors)
  • Added explicit types where needed (312 errors)
  • Updated third-party type definitions
Week 3: Cleanup
  • Fixed remaining edge cases
  • Added custom type guards
  • Documented patterns for team
typescript
// Before: Implicit any everywhere
function processData(data) {
  return data.map(item =

item.value);

}

// After: Explicit, safe types

function processData(data: DataItem[]): number[] { return data.map(item =

item.value);

}

// Type guard for runtime safety

function isDataItem(obj: unknown): obj is DataItem { return typeof obj === 'object' && obj !== null && 'value' in obj; }

What We Found

Error Breakdown:
CategoryCountSeverity
Null/Undefined423Critical
Implicit Any312High
Property Access67Medium
Function Types45Low
The Scariest Finds:
  • 12 cases where null could reach database queries
  • 8 cases where undefined could be sent to payment APIs
  • 3 cases where type coercion hid data corruption

"Each of those 12 null cases was a potential production incident waiting to happen."

The Playbook

Phase 1: Enable strictNullChecks Only
json
{
  "compilerOptions": {
    "strictNullChecks": true
  }
}
This catches the most critical bugs. Fix these first. Phase 2: Add noImplicitAny
json
{
  "compilerOptions": {
    "strictNullChecks": true,
    "noImplicitAny": true
  }
}
Forces explicit typing. Improves code readability. Phase 3: Full Strict Mode
json
{
  "compilerOptions": {
    "strict": true
  }
}
Enable everything. Your code is now bulletproof. Pro Tips:
  • Use // @ts-expect-error

    sparingly during migration

  • Create type guards for external data
  • Update tests to handle null cases
  • Document common patterns for your team
Need help migrating to strict mode? We've done it for 50K+ line codebases.

Need this implemented in your business?

We turn these insights into production-ready systems. From AI integrations to enterprise platforms.