I want to get the embarrassing part out first. I was a TypeScript sceptic for about three years after it became mainstream. My objections were: the type system adds verbosity, you spend time fighting the compiler instead of writing features, and JavaScript's dynamic nature is a feature not a bug. I still think some of that is defensible. I was still wrong about the overall conclusion.

What Actually Changed After Switching

We migrated our main product codebase from JavaScript to TypeScript about two years ago. The migration itself was not painful — we did it gradually, starting with the utility modules and working outward. It took about four months to get to "TypeScript everywhere" and another month to turn on stricter compiler settings.

The runtime error rate dropped significantly. We track errors via Sentry. In the six months before the TypeScript migration, we averaged 3.2 novel production JavaScript runtime errors per week (not counting known issues in the backlog). In the six months after the migration was complete, the average was 0.7. Almost all of the remaining errors are from user-land data — malformed API responses, unexpected third-party data — rather than internal logic bugs.

TypeScript doesn't eliminate bugs. It eliminates a specific class of bugs — the ones where you passed an argument in the wrong order, where you assumed an object had a property it might not have, where you forgot to handle a null case. These are embarrassingly common in large JavaScript codebases and TypeScript makes them impossible to ship.

The Refactoring Difference

This was the benefit I didn't anticipate as strongly. Refactoring in TypeScript is a fundamentally different (better) experience. When you rename a function, the compiler tells you every call site. When you change an interface, every usage that doesn't match the new shape becomes a compile error. The feedback loop for large-scale changes is dramatically tighter than it is in JavaScript, where you're reliant on test coverage to catch everything you missed.

The Ongoing Frustrations (Being Honest)

Third-party type definitions are inconsistently maintained. Some popular packages have excellent types; others have types that are technically present but wrong, which is arguably worse than having none (you trust them and they mislead you).

Very complex generic types are genuinely difficult. I occasionally write something that should work, the compiler disagrees, and I end up with a // @ts-expect-error comment that I'm not proud of.

Build times are longer. In a large codebase, the TypeScript compilation step adds meaningful time to both local development and CI. There are ways to mitigate this (project references, incremental compilation) but it's real overhead.

The Bottom Line

For any codebase with more than three developers or more than three months of expected lifespan, TypeScript is worth the investment. The reduction in runtime errors alone justifies it. The refactoring improvements pay dividends every time you change something central. The friction is real but decreasing as tooling improves. I was wrong to resist it for as long as I did.