# TypeScript Strict Mode: Worth the Pain? When we enabled strict mode on our codebase, the compiler produced 1,247 errors. On a Friday afternoon. Nobody was happy. Two weeks later, every error was fixed, and in the following six months, our production bug rate dropped by 34%. Here's the full story. ## What Strict Mode Actually Enables TypeScript's "strict" flag is a shorthand for enabling several individual checks. The most impactful ones: ### strictNullChecks Variables are not automatically nullable. If a function might return null, the type must say so explicitly. Without this flag, every variable in TypeScript can secretly be null — and the compiler won't warn you when you forget to check. This single flag catches the most bugs. Tony Hoare called null references his "billion dollar mistake." strictNullChecks is the fix. ### noImplicitAny Every variable must have a type. If TypeScript can't infer it, you must declare it. Without this flag, untyped variables silently become "any" — which means TypeScript stops checking them entirely. The "any" type is TypeScript's escape hatch, and without noImplicitAny, it's an invisible escape hatch. Code that looks type-safe isn't. ### strictFunctionTypes Function parameter types are checked more precisely. Without this flag, TypeScript allows unsound function type assignments that can cause runtime errors. ### strictPropertyInitialization Class properties must be initialized in the constructor or declared as potentially undefined. This prevents the common bug of reading a property before it's set. ## The Migration: What to Expect Enabling strict mode on an existing codebase is a project, not a setting change. Here's what we encountered: ### Phase 1: The Error Flood (Day 1) 1,247 errors. Most fell into predictable categories: - **Missing null checks:** 45% of errors. Function returns that could be undefined but weren't handled. - **Implicit any:** 30% of errors. Callback parameters, dynamic object access, and untyped third-party library usage. - **Uninitialized properties:** 15% of errors. Class properties without default values or constructor initialization. - **Genuine bugs:** 10% of errors. Code that was actually broken but TypeScript wasn't catching. That last category is the payoff. 10% of strict mode errors were real bugs hiding in our code. In our case, that was 125 potential production issues. ### Phase 2: Systematic Fixing (Week 1-2) We approached it methodically: **Easy fixes first.** Adding null checks and type annotations covered 70% of errors. These are mechanical changes — add a "| null" to a type, add an "if (x != null)" check before accessing x. **Library types.** Missing or incorrect type definitions for third-party libraries caused 15% of errors. Installing updated @types packages or adding thin type wrapper modules resolved most. **Design changes.** 15% of errors required actual code changes — restructuring functions that relied on implicit null behavior, adding proper error handling, or redesigning class initialization. ### Phase 3: Ongoing Vigilance (Permanent) With strict mode enabled, every new line of code is checked. New team members who try to cut corners with "any" get a compiler error. The codebase's type coverage only improves from here. ## The Results: Six Months Later ### Bug Reduction Production bugs related to type errors (null reference, wrong type, missing property): down 34%. These weren't edge cases — they were bugs that would have reached users and caused errors, support tickets, or silent data corruption. ### Code Review Speed Code reviews got faster because reviewers spent less time checking for type-related issues. The compiler handles that now. Reviewers focused on logic, architecture, and readability instead. ### Onboarding Improvement New developers got productive faster because the type system acts as documentation. When a function's signature says exactly what it accepts and returns — including null cases — new developers don't need to read the implementation to use it correctly. ### Refactoring Confidence Renaming a property, changing a function's return type, or restructuring a module produces immediate compiler feedback on every affected file. No more "I think I found all the usages" guesswork. ## The "any" Discipline Strict mode doesn't eliminate "any" — it makes it explicit. We still use "any" in specific situations: - **Third-party library boundaries** where proper types don't exist - **Truly dynamic data** that can't be typed at compile time (rare) - **Migration shims** that will be properly typed later Every use of "any" in our codebase has a comment explaining why. This is a team convention, not a compiler requirement, but it works. "any" is a conscious decision, not an accidental omission. Our target: less than 0.5% of type annotations should be "any". Currently we're at 0.3%. ## Common Objections ### "It slows development" Short-term, yes. Adding types to untyped code takes time. Long-term, no. Less debugging, fewer production incidents, and faster refactoring more than compensate. Our team's velocity (features per sprint) increased by 15% after the initial migration period. ### "It's too verbose" TypeScript's type inference handles most cases. You rarely need to write explicit type annotations — the compiler infers them from usage. Strict mode doesn't add verbosity; it adds precision. ### "Some code can't be typed" True, but less than you think. We found that 98% of our "untyped" code was actually very typeable — we just hadn't bothered. The remaining 2% uses carefully scoped "any" with documented justification. ### "Our codebase is too big to migrate" You can enable strict mode incrementally. Start with strictNullChecks on new files only (using per-file overrides or a tsconfig path filter). Gradually extend to existing files as they're modified. ## Our Recommendations **For new projects:** Enable strict mode from day one. There's no reason not to. The cost of starting strict is nearly zero; the cost of migrating later grows with every line of code. **For existing projects under 50K lines:** Allocate a sprint for migration. It's worth the investment. Most codebases this size can be migrated in 1-2 weeks. **For existing projects over 50K lines:** Migrate incrementally. Enable strict mode for new code. Fix existing code as files are touched for other reasons. Set a deadline (6-12 months) for full migration. **For teams new to TypeScript:** Start with strict mode anyway. It's harder to learn TypeScript without strict mode and then "unlearn" the bad habits than to learn it correctly from the start. ## The Bottom Line TypeScript without strict mode is like wearing a seatbelt but not fastening it. You have the safety equipment, but you're not using it. Strict mode is what makes TypeScript's type system actually useful — not as documentation, but as a guarantee. Those 1,247 errors on that Friday afternoon? Every single one was a place where our code was wrong and we didn't know it. Now we know. And more importantly, the compiler catches the next one before it reaches production.