The Quiet Cost of Skipping Type Safety
Skipping type safety is a decision that looks free at the start and reveals its cost over eighteen to twenty-four months. The symptoms are not dramatic. They are slow: bugs that require a debugger to trace, refactors that take three times the estimate, and engineers who spend more time reading code than writing it. I have cleaned up the aftermath of this decision enough times that I no longer see it as a style preference.
Written by Yashveer Singh, founder of Yashveer Labs.
What you actually need to know
- The cost of skipping type safety is not a one-time payment. It is a slow tax on every hour of development.
- Type errors that appear in production are harder to fix than type errors that appear in the editor.
- The migration cost from JavaScript to TypeScript is front-loaded and visible. The cost of not migrating is back-loaded and invisible.
- Strict mode matters. TypeScript without strict mode catches a fraction of what it could.
- Runtime validation and compile-time type safety are not the same thing. You need both.
| Approach | Type safety level | When bugs surface | Refactor difficulty |
|---|---|---|---|
| Plain JavaScript, no checks | None | Production | Very high |
| JavaScript with JSDoc types | Low | Editor (partial) | High |
| TypeScript without strict mode | Medium | Editor and build | Medium |
| TypeScript with strict mode | High | Editor and build | Low |
| TypeScript + Zod runtime validation | Very high | Editor, build, and runtime | Lowest |
The core argument
The argument for skipping type safety is always speed. The team wants to move fast. Adding types feels like friction. The product is changing, the data model is not stable yet, adding types to things that will change tomorrow seems wasteful. This reasoning is coherent in week one.
By month six, the reasoning has started to work against the team. The data model changed four times and none of the changes were tracked anywhere except in the code. The function that used to take a user object now takes a user ID in some callers and a full user in others, because someone changed the signature and the callers updated inconsistently. The refactor that was supposed to take two days has been sitting in a branch for a week because nobody is sure what breaks.
These are not rare edge cases. They are the predictable outcome of a codebase where the type information exists only in the developer's head. Every developer who touches the code has to reconstruct that information from reading. Experienced developers do this faster. New developers do it slowly and make mistakes doing it. The cost compounds every time the team grows.
The quiet part is that this cost never shows up as a line item. It shows up as slow sprints, as bugs that take longer to debug than they should, as refactors that get abandoned because the scope is unclear. The team feels the friction without being able to name it. They attribute it to complexity or code quality in general terms. Type safety would have made the cost visible and eliminated most of it.
Migrating a JavaScript codebase to TypeScript
Phase one: partial wins with allow-js and checkJs
The first phase does not require converting any files. Turning on allowJs and checkJs in the TypeScript config gives the type checker permission to inspect JavaScript files. The feedback is weaker than full TypeScript, but it surfaces a real class of errors immediately, without any file conversions.
This phase takes a day to set up and usually surfaces between ten and forty warnings in a medium-sized codebase. Each warning is a real bug risk. Fix them, leave the config in place, and the team has started the migration without disrupting any existing workflow.
Phase two: convert the high-risk modules
Start converting files to TypeScript at the modules that carry the most risk: payment logic, authentication, data export, anything that touches the database schema. These are the modules where a type error causes a production incident. Converting them produces the highest return per hour of migration work.
Use strict mode on every converted file from day one. Converting a file to TypeScript without strict mode produces a type-unsafe TypeScript file that catches very little. The benefit of strict mode is not incremental. It is most of the benefit.
Phase three: extend coverage over time
After the high-risk modules are converted, extend TypeScript coverage file by file as the team touches existing code. The rule is that every file changed for any reason gets converted in the same PR. This ties the migration to the natural rhythm of feature work and avoids a separate migration project that stalls.
How long does it take
| Codebase size | Migration estimate | Recommended approach |
|---|---|---|
| Under 10k lines | 1-3 days | Convert all at once |
| 10k to 50k lines | 2-4 weeks | Phase by risk level |
| 50k to 150k lines | 6-12 weeks | Phase by module, file-by-file rule |
| Over 150k lines | 3-6 months | Long-running migration with dedicated effort |
These estimates assume strict mode from the start on converted files. Adding strict mode to a partially migrated codebase retroactively is more expensive than getting it right the first time.
What to look for when evaluating type safety debt
- Functions that accept an
objectoranyparameter where a specific shape is clearly expected. - API response handling where the team accesses properties without checking if they exist.
- Database queries where the result shape is assumed but not validated.
- Props in a React codebase with no type definitions.
- Shared utility functions with no documented parameter types.
- Post-mortems where the root cause was "wrong shape of data passed to function."
- New engineer onboarding that requires extensive code reading to understand data models.
Expert opinion
Type safety is not about writing less code. It is about making the code you write say what it means. When the code says what it means, the next engineer, including you in six months, can read it and trust it. When it does not, every read becomes an investigation. I have watched teams spend more time on type-related debugging than they would have spent adding types in the first place.
>
Yashveer Singh, founder of Yashveer Labs
How this played out on a real project
A two-year-old Node.js API came in for a refactor engagement. Plain JavaScript throughout, no type annotations, a mix of callbacks and promises that had been partially migrated. The team's biggest complaint was that every feature took twice as long as estimated because they never knew what shape the data was in when it arrived at any given function.
We ran the two-day audit and the type-related friction showed up in every conversation. Four of the last six production incidents involved wrong data shapes. Two were caught in staging. Two were not.
We converted the API's core modules to TypeScript with strict mode over three weeks. The first week alone found eleven latent bugs that had not yet manifested as incidents. One of them was in the billing calculation module. It had been there for seven months.
Six weeks after the migration, the team's bug rate on the converted modules dropped by roughly sixty percent. The refactor estimates became reliable again. The onboarding time for a new engineer dropped from two weeks to four days on the core modules.
For teams weighing whether the migration is worth it, when to refactor and when to rewrite covers the broader question of when a migration investment pays off. For the test coverage that should accompany any serious type safety migration, adding tests to a legacy codebase without going mad covers the right sequencing.
Common mistakes teams make
- Converting files to TypeScript without enabling strict mode. The result is a TypeScript codebase that catches far less than it should and gives false confidence.
- Adding
anytypes to make the migration faster. A codebase full ofanyis JavaScript with extra steps. - Skipping runtime validation on external data. TypeScript does not protect you from a malformed API response or a database migration that changed a column type.
- Migrating all files at once in a big bang without a safety net. Large conversions on untested modules produce migration bugs that are hard to isolate.
- Not setting up the TypeScript config correctly before starting. The compiler options matter as much as the type annotations.
- Treating type safety as a separate project rather than folding it into normal feature work.
- Stopping at partial migration and leaving the high-risk modules in JavaScript. The modules that need types most are the ones that justify the migration.
A six-week migration plan
- Week one. Enable
allowJsandcheckJs. Fix the warnings this surfaces. Set up the TypeScript config with strict mode ready for converted files. - Week two. Identify the five highest-risk modules using the tech debt audit output or a quick review of incident history.
- Week three. Convert the top two high-risk modules. Add Zod validation for their external data inputs.
- Week four. Convert the remaining three high-risk modules. Write or improve tests for the converted modules.
- Week five. Introduce the file-by-file rule: any file touched for a feature change gets converted in the same PR.
- Week six. Review coverage, update onboarding docs to reflect the new type conventions, and set a target for full conversion.
The broader context for this kind of investment sits in why TypeScript almost always pays off in SaaS, which covers the business case in more detail.
Frequently asked
Why you should hire Yashveer Singh for this
The kind of work this article describes is the kind of work I do every week. Production deployments, scaling decisions, the architecture choices that compound over years. I am Yashveer Singh, founder of Yashveer Labs. If you need this done, I do not need to be sold on the brief. Send me what you have and I will tell you what it actually takes.
Posts that line up with this one.
- Tech Debt and Refactoring
Migrating From Express to Fastify or NestJS or Beyond
Express still works but it shows its age in production. Here is when to migrate, which framework to migrate to, and how to do it incrementally without breaking the application that customers depend on.
- Tech Debt and Refactoring
Migrating From REST to GraphQL: A Strategic Read
GraphQL solves real problems but introduces its own. The migration from REST to GraphQL is not a performance upgrade; it is an architectural shift. Here is when it is worth it and how to do it without breaking existing clients.
- Tech Debt and Refactoring
Mutation Testing: A Discipline Worth Considering
High code coverage does not mean good tests. Mutation testing reveals whether your tests actually catch bugs. Here is what it is, when it adds value, and how to introduce it without adding meaningless overhead.
- Tech Debt and Refactoring
Refactor Stories That Killed a Startup
Refactoring is necessary and valuable. It is also one of the most reliable ways to destroy momentum at the wrong moment. These are the patterns that turn a reasonable engineering goal into a business catastrophe.