Why Software Rewrites Usually Fail—And the Cases Where They Work
July 7, 2026
The impulse to rewrite an existing codebase from scratch is one of the most common and most consequential decisions in software engineering. It’s also one of the most reliably underestimated in terms of time, cost, and risk. Joel Spolsky’s 2000 essay “Things You Should Never Do” articulated the classic case against rewrites—arguing that existing code, however messy it appears, contains years of accumulated bug fixes and hard-won knowledge about edge cases that are invisible until you’ve thrown them away and users start reporting the same bugs the old code had fixed years earlier. The basic argument has been reinforced by subsequent experience across many organisations.
And yet: some rewrites succeed. Understanding what distinguishes the rewrites that improve a system from those that delay it for years while introducing new problems requires being specific about what type of rewrite is being considered, what the existing system’s actual problems are, and what conditions are necessary for a rewrite to accomplish its goals.
Why Rewrites Are Seductive
The pressure to rewrite usually builds from accumulated frustration with a codebase that has become difficult to work with. Features that should be simple take weeks. Every change seems to break something elsewhere. New engineers struggle to understand the code. The architecture is inconsistent, the naming is confusing, the abstractions are wrong. The existing code has accreted fixes and workarounds over years until the original design is barely visible beneath the layers.
The seductive logic of a rewrite is that if you could start with what you know now, you’d design it better. You’d use the right architecture, choose better abstractions, write cleaner code. The existing system would be replaced with something you could actually work with.
The flaw in this logic is that you’re comparing the existing system—with all its problems visible—to an imaginary future system that doesn’t yet have problems. The future system will have different problems, and you’ll discover them as you build it. The bugs you’re fixing in the existing system will be bugs you need to find and fix again in the new system. The edge cases that the existing system handles correctly (often in obscure code that looks wrong but isn’t) will be edge cases you need to re-discover in the new system, typically through user reports.
The Underestimation Problem
Software rewrites consistently take longer and cost more than estimated for a structural reason: the estimate for the rewrite is compared against the effort to maintain and extend the existing system, but the comparison is wrong. The rewrite estimate accounts for recreating the known functionality; it doesn’t account for the accumulated bug-fixing, edge case handling, and implicit knowledge in the existing system that won’t be visible until it’s missing.
The typical rewrite experience: the new system reaches feature parity with the old system at 60–80% of the estimated timeline, at which point the remaining work turns out to be harder than expected because it involves the parts of the system that are difficult for a reason. Launch is delayed. The new system goes live with some regressions that take months to fix. The original estimate expands by 2–3x. The team that did the rewrite is exhausted.
Meanwhile, the business continued generating requirements during the rewrite period. The old system received maintenance fixes. The new system, because it was in progress, received minimal new feature development. By the time the rewrite is complete, the team has been slowed for the duration of the rewrite and has a significant backlog to address.

The Cases Where Rewrites Work
The consistent pattern in rewrites that succeed is that they are scoped significantly more narrowly than “rewrite the system.” A complete rewrite of a non-trivial production system is rarely the right answer; a scoped rewrite of a specific component or service, executed while the rest of the system continues to operate, is often the right answer.
Strangler Fig pattern. Martin Fowler’s “Strangler Fig” pattern describes incrementally replacing parts of a system by building new functionality in the new architecture alongside the old system, gradually routing traffic from old to new, and eventually decommissioning the old code once the new version handles all its functionality. This avoids the big-bang risk of a complete rewrite while achieving the same architectural goal over time. It’s slower than a clean-room rewrite and requires maintaining compatibility at the interfaces—but it allows the existing system to continue operating while the new system is built, and allows detecting and fixing problems incrementally rather than all at once at launch.
Narrow scope with clear boundary. Rewrites of a specific, well-isolated component—a payment processing module, a data ingestion pipeline, a reporting system—can be successful because the scope is bounded, the interface with the rest of the system is defined, and the rewrite can be validated against the existing system’s behavior before cutover. The component being rewritten should be genuinely isolated and the interface should be genuinely stable.
Genuine technology migration requirement. When an existing system runs on a technology that is end-of-life, no longer secure, or fundamentally incompatible with a required capability, a technology migration rewrite has a clearer justification than one motivated by code quality. The goal is technology platform migration rather than architectural improvement—a more constrained scope that’s easier to execute.
Starting product with no users. A pre-launch product where the code is genuinely not working and there are no users to disrupt is a legitimate rewrite candidate. The costs of a rewrite are lowest when there’s no production traffic and no accumulated user expectations to preserve. This is common in early startup contexts where initial implementation choices were genuinely wrong and the team has the information to do better.
What to Do Instead of a Rewrite
Most codebases that feel like candidates for a rewrite are actually candidates for sustained, structured improvement. This is less exciting than a clean-room rewrite but has a much better success rate and avoids the cost of rebuilding functioning systems.
The key practices: identify the specific parts of the code that are causing the most friction (often a small fraction of the codebase accounts for most of the maintenance difficulty), refactor those parts specifically rather than everything, write tests for the code you’re refactoring before refactoring it (to ensure you haven’t broken existing behaviour), and improve architectural consistency at the module boundary level before attempting deeper refactoring.
This approach is slower to produce visible architectural improvement but faster to produce working features, and it accumulates the knowledge embedded in the existing system rather than discarding it. Most experienced engineering teams that have executed both complete rewrites and sustained incremental improvement conclude that incremental improvement is underrated and complete rewrites are overrated—not because rewrites never work, but because the conditions for a successful complete rewrite are less common than engineers believe when they’re frustrated with the existing code.