Most rewrites are a mistake, and not because the old code is good. A rewrite bets the roadmap on the theory that a second team, under the same pressure, with the same domain gaps, will somehow avoid the first team’s mistakes. Usually it does not. Teams reach for a rewrite because the code is ugly, the original authors left, or a new framework looks cleaner, and none of those are architecture problems. This framework treats the call as a risk-management problem: refactor by default, and rewrite only under named conditions you can defend to a board.
What happens by default: the trigger is a feeling. The code is ugly, the framework is old, “we would do it differently now.” Someone builds a greenfield estimate with no migration, no dual-run, and no data backfill in the plan, then freezes features on the old system while the new one is built. Success gets measured by “it is clean,” and the rewrite reimplements bugs nobody documented, only to rediscover them in production.
What good risk management does: the trigger is a named constraint, an SLA or a tenancy model or a compliance boundary you genuinely cannot reach in place. You score the system first and refactor unless the rubric and the conditions both point to rewrite. New runs beside old, traffic moves slice by slice, and characterization tests capture current behavior before anything moves, so the edge cases survive.
When a system is healthy enough to bend, you improve it in place. Change failure rate is manageable, seams exist, and the pain is real but the structure still holds. Any rewrite talk here is taste, not evidence. Invest in tests and seams, and re-score in a couple of quarters if the pain grows.
When one or two subsystems are past saving but the whole is not, you replace those behind a routing facade. New runs beside old, you move the lowest-risk slice first, dual-run and compare on real traffic, and keep a per-slice rollback. The old system keeps shipping features throughout, so value and learning land every slice instead of at a distant finish line.
A full rewrite is defensible only when the architecture blocks the roadmap and no incremental path clears it: a structural constraint, an end-of-life or unhirable platform, a compliance boundary the schema cannot hold, or economics baked into the design. Even then, sequence it as a strangler-fig, write characterization tests first, and pass the pre-mortem before anyone signs.
Rate change failure rate, coupling, test coverage, key-person risk, business velocity, compliance debt, and downtime cost from 1 (refactor) to 5 (rewrite pressure), times their weight. The rubric is a check on your gut, and it is deliberately hard to trip into “rewrite.” A high score alone is not a green light.
Answer five questions in order, and the first “yes” that survives scrutiny is your answer. Is a roadmap item blocked by the architecture, not the code style? Can refactoring clear it in about two quarters? Can you carve the capability behind a facade? Most teams stop at the second or third question, which is the point.
If you must replace, add a routing facade, characterize the current behavior of each slice before you move it, and shift traffic gradually with the rollback route kept warm. Delete the old slice only after the new one has carried full traffic cleanly. Retire code last, not first.
Assume the rewrite already failed, 18 months on, cancelled, old system still in production, and write down why. If you cannot answer every line with evidence (named constraint, migration in the estimate, domain knowledge secured, budget that survives a 2x overrun), you are not ready to sign.
Fast generation of unverified code into a system nobody fully understands is how you rebuild the same bugs faster. AI makes a rewrite feel cheaper, and that is exactly the trap, because speed of authoring is not speed of understanding. Refactor by default. Reach for a rewrite only when a named constraint, not a bad feeling about the code, forces your hand, and even then move slice by slice with a rollback and a pre-mortem behind you. The most expensive call you make is the one you make on aesthetics.
Cleaner code is not the goal, an unblocked roadmap is. A mess is a knowledge and discipline problem, and a rewrite carries both forward while throwing away the hardest asset to rebuild: the thousands of edge cases the current system already handles correctly. Refactor behind tests and seams, and you get the cleanliness without betting the quarter.
When the architecture, not the code, blocks the roadmap and refactoring cannot clear it in a reasonable window. Concretely: a single-tenant design when you now sell multi-tenant, a platform at end of life or unhirable, a compliance boundary the schema cannot enforce, or unit costs baked into the design. Ugly code and unfashionable stacks are not on that list.
CTOs, VPs of Engineering, and staff engineers facing a legacy-system call, plus technical investors assessing whether a portfolio company’s rewrite plan is evidence or aesthetics.
AI writes roughly 26.9% of production code and adoption is near universal, so a rewrite feels cheaper than ever. That is the trap. Only 29% of developers trust AI output, so generated modules still need review and tests. Faster authoring into a system nobody fully understands rebuilds the same bugs faster, not fewer.
Strangler-fig, not big-bang. Put a routing facade in front, write characterization tests to pin current behavior, move the lowest-risk slice first, dual-run and compare, and shift traffic gradually with a per-slice rollback. The old system keeps shipping the whole time, so a bad estimate self-corrects early instead of at a cliff.