When we adopted TypeScript strict mode across our entire codebase at Deveote, it was painful. We spent two weeks fixing over two hundred type errors that had been lurking in our code, many of them hiding genuine bugs that would have eventually caused production issues. That initial investment has paid for itself many times over, and we now consider strict mode non-negotiable for all new projects. This article explains why we made this choice and shares the patterns that make strict mode manageable in a large Next.js application.
What Strict Mode Actually Does
TypeScript's strict mode is not a single setting. It is a collection of compiler flags that each enforce a different aspect of type safety. The most impactful are strictNullChecks, which forces you to handle null and undefined values explicitly; noImplicitAny, which requires type annotations for values that TypeScript cannot infer; and strictFunctionTypes, which enforces correct function parameter types. Together, these flags eliminate entire categories of runtime errors: null reference exceptions, unexpected undefined values, and type mismatches that only manifest under specific conditions.
The Cost of Implicit Any
The noImplicitAny flag is the one that causes the most friction for teams adopting strict mode. Without it, TypeScript silently assigns the any type to values it cannot infer, which effectively disables type checking for those values. This is particularly insidious because the code appears to be type-checked but actually is not. We have found that map callbacks, event handlers, and dynamic object access are the most common sources of implicit any in our codebase.
Common Patterns for Strict Mode Compliance
Through our experience fixing hundreds of strict mode violations, we have identified several patterns that appear repeatedly. Understanding these patterns makes it much easier to write strict-mode-compliant code from the start.
Typing Map Callbacks
The most frequent strict mode violation we encounter is in map callbacks on arrays. When TypeScript cannot infer the array element type, the callback parameter becomes implicit any. The fix is straightforward: annotate the callback parameter with the array element type. We use the pattern array.map((item: typeof array[number]) => ...) which automatically derives the correct type from the array type.
Prisma Transaction Types
Prisma's transaction callback is another common source of implicit any errors. The transaction client parameter needs to be explicitly typed as Prisma.TransactionClient. This is a Prisma-specific pattern that every engineer on our team learns during onboarding. We have a lint rule that flags untyped transaction callbacks to catch this early.
Object.entries and Object.keys
TypeScript's handling of Object.entries and Object.keys is notoriously loose. Both return string-typed keys, even when the object has more specific key types. When using map on the result, you need to explicitly type the destructured tuple: Object.entries(obj).map(([key, value]: [string, ValueType]) => ...). This pattern is verbose but necessary for strict compliance.
Gradual Adoption Strategy
If you have an existing codebase without strict mode, the prospect of enabling it and facing hundreds of errors can be daunting. We recommend a gradual approach. Start by enabling strict mode but using ts-expect-error comments to suppress existing violations. Then systematically fix the violations file by file, removing the suppression comments as you go. Set a target of fixing ten violations per sprint, and track progress on a dashboard. This approach allows you to get the benefit of strict mode for new code immediately while addressing existing violations over time.
The Build Pipeline
Once strict mode is enabled, it must be enforced in your CI/CD pipeline. We run the TypeScript compiler as part of our build process, and any strict mode violation fails the build. This prevents new violations from being introduced, even as the team works through existing ones. Our deployment to Railway runs in strict mode, which means any type error that slips past local development will be caught before it reaches production.
Developer Experience Considerations
Strict mode can feel like it slows down development, especially for engineers coming from dynamically typed languages. We mitigate this by investing in tooling: VS Code settings that highlight type errors inline, shared type definitions that cover common patterns, and code snippets for frequently used typed patterns. We also pair new team members with experienced TypeScript developers during their first few weeks to help them develop intuition for the type system.
Conclusion
TypeScript strict mode is an investment in code quality that compounds over time. Every type error caught by the compiler is a bug that never reaches your users. Every explicit type annotation is documentation that helps the next engineer understand your code. The initial cost of adoption is real, but the ongoing benefits in reliability, maintainability, and developer confidence make it one of the best engineering decisions we have made at Deveote.