skip to content

You own a large, actively developed JavaScript codebase and the team wants type safety. How would you decide between an incremental TypeScript migration, a rewrite in TypeScript, and staying on JavaScript with types checked by tsc?

level: principalimportance: should knowfreq 35%

answer

  1. price each option, don't argue the tech
  2. a rewrite forks and freezes
  3. incremental keeps shipping daily
  4. same checker without renaming files
  5. declare the end state up front

basics

~20 s

Incremental is the default because the codebase stays shippable and every step is revertable. A rewrite is only defensible for a small, stable, well-covered module. Staying on typed JavaScript is the right answer when the build cannot change or the code ships as source.

solid answer

~60 s

Start from what each option costs. A rewrite forks the codebase, competes with feature work for months, and discards years of accumulated bug fixes encoded in code nobody remembers writing — worth it only for a small, stable, well-tested component. Incremental migration is the default: both extensions compile together, the product ships every day, and each file is a revertable change; you pay in a long period where the codebase is visibly mixed. Checking types in JavaScript sources with tsc, no file renames at all, is genuinely the right call when there is no build step to change, when the package ships as source, or when the team's appetite for touching every file is low — you get the same checker for a fraction of the disruption, at the cost of more verbose syntax and a few awkward corners. The decision inputs are whether a compile step already exists, test coverage, churn rate, how long the code will live, and team appetite. Then define what done means, because a permanently mixed codebase is a legitimate end state if it is chosen rather than drifted into.

go deeper

for a junior

Know that TypeScript and JavaScript files can be compiled together, so adopting types does not require rewriting a codebase all at once.

for a middle

Be able to compare the options on concrete costs — a rewrite forks the codebase, incremental keeps it shippable, typing in place skips renames entirely — rather than on preference.

for a senior

Show that you would gate the choice on evidence: existing build step, test coverage, churn, remaining lifetime, and the defect classes types would actually have caught in recent incidents.

for a principal

Own the end state and the ratchet. Declare what done means, put CI mechanisms behind it so the migration cannot silently stall, assign the work by ownership, and be willing to stop at a deliberate mixed state when the payback is not there.

## Frame the decision, not the technology The question is never "is TypeScript good". It is: what buys the most defect reduction per engineer-week, given this codebase, this team, and this roadmap. Three options are on the table, and the judgment being assessed is whether you can price each one honestly. ## Option one: rewrite A rewrite is the option that feels fastest and almost never is. Its costs are structural. You fork: two codebases now exist, and every bug fix and feature has to be applied twice or held until the switch. You lose the encoded history — the odd branch that handles a specific customer's malformed input, the workaround for a browser quirk — which looks like cruft to a rewriter and is regression bait. And you have no incremental value: nothing ships until it all ships, so the project is uniquely vulnerable to a change in priorities halfway through. It is defensible in a narrow case: a small, well-bounded module with strong test coverage and stable requirements, especially one you were going to rework anyway. Then "rewrite" is really "convert with an implementation change", and the tests carry the risk. ## Option two: incremental migration This is the default for a reason. The compiler accepts both extensions in one program, so the codebase is always in a shippable state, and conversion proceeds file by file with each step small enough to review and revert. Value arrives continuously: the first converted modules start catching bugs while the rest of the tree is untouched. The honest costs are a long tail and a visibly mixed codebase. Mid-migration, a reader has to know which conventions apply where, and a half-strict configuration gives weaker guarantees than either end state. Momentum is the real risk: without a ratchet in CI and named ownership, migrations stall at seventy percent and stay there for years. The inputs that argue for it: an existing build step, a codebase that will live for years, decent test coverage, and a team that will keep touching the same files anyway. ## Option three: type the JavaScript in place Often underrated. The compiler can check JavaScript sources directly, with types expressed in comment syntax, and no file is renamed. You get the same checker, the same errors, the same editor support — with the disruption of a migration removed. This wins outright in specific situations: there is no compile step and adding one is a bigger change than the typing itself; the package ships as source and consumers run it directly; the code is short-lived; or the organisation cannot absorb a change that touches every file. Its costs are real but bounded: the syntax is verbose, a few type-level constructs are awkward to express, and hiring or onboarding assumes the mainstream form. It is also a legitimate *stage*: type in place first, prove the value with real defects caught, then rename later if the case is made. That sequencing lets you decide with evidence instead of conviction. ## The decision inputs - **Is there already a build step?** If yes, incremental migration is nearly free to start. If not, the honest comparison is against typing in place. - **How is it distributed?** A library that ships source, or code executed directly by a runtime, tilts toward typing in place. - **Test coverage.** Coverage is what makes conversion edits safe; without it, every migration commit is unverified. - **Churn.** High-churn files are painful to convert (merge conflicts) but pay back most, since they are edited constantly. Dead-quiet code pays back least — and code scheduled for deletion pays back nothing. - **Team appetite and skill.** A migration that the team does not believe in stalls, and a stalled migration is worse than none: you carry the mixed-codebase cost without reaching the benefit. - **Time horizon.** Under a year of remaining life, the payback usually is not there. ## Define done before you start The most common failure is not choosing wrong; it is never declaring an end state. Write it down: full strict everywhere, or strict in the product tree with two legacy directories excluded until they are deleted, or typed-in-place indefinitely. Then put a ratchet behind it — a CI gate that stops new untyped files appearing and holds the outstanding-error count so it can only fall — and assign the remaining work by directory to the teams that own it. ## Measure it Defend the choice with numbers rather than taste: the share of the codebase under checking, the outstanding error count trending down, and the classes of production incident that types would have prevented. If a quarter of migration effort has not moved any of those, that is evidence to change strategy — including to stop where you are and call the mixed state the answer.

  • What would make you choose a rewrite over an incremental migration?
    A small, well-bounded module with strong test coverage and stable requirements — ideally one already scheduled for rework, so the type change rides along with work you were funding anyway. The tests are what make it defensible: they carry the regression risk that the rewrite reintroduces. Applied to a large, actively developed codebase, the same reasoning gives the opposite answer.
  • How do you know a migration is stalling rather than progressing slowly?
    Watch the ratchet, not the sentiment. If the share of checked code and the outstanding error count are flat across a quarter while feature work continues, it has stalled. The usual cause is unowned work: no directory assigned to a team, and no CI gate preventing new untyped files, so the migration competes with roadmap items and loses every time.
  • Is a permanently mixed codebase an acceptable end state?
    Yes, when it is chosen. Code on its way out earns nothing from being typed, and forcing uniformity there is waste. What makes it acceptable is that the boundary is explicit in configuration, CI stops the untyped area from growing, and the team knows this is the destination — the failure mode is drifting into a mix and calling it a plan afterwards.
  • How would you present this decision to a sceptical engineering leader?
    In their terms: cost, risk, and payback. Show the defect classes the checker would have caught from the last two quarters of incidents, the engineer-weeks each option costs, and the fact that incremental delivers value continuously and can be stopped at any point without waste. Offer the smallest reversible first step rather than asking approval for the whole programme.

saying these in an interview costs you the question

  • Recommends a full rewrite for type safety
  • Ignores that a rewrite forks and freezes the product
  • Dismisses typing JavaScript in place as not real typing
  • Starts without defining what done means
  • Judges success by files renamed rather than defects prevented

context