When to Rebuild Your Software vs Keep Iterating
The rewrite is one of the most tempting — and most dangerous — decisions in software. Here's how to know when it's genuinely necessary and when iteration is still the right call.
The rewrite is one of the most seductive ideas in software engineering. The codebase has accumulated debt, features take longer than they should, engineers complain about the state of the system — and the obvious-seeming solution is to start fresh. Build it right this time, with everything you now know.
It's a compelling narrative. It's also one of the most reliable ways to spend 12 months of engineering time and emerge in roughly the same position you were in before.
This guide is about how to think through the decision honestly — including when a rewrite genuinely is the right answer.
Why Rewrites Usually Fail
The case for a rewrite rests on a premise that's almost always wrong: that the team building the new system understands the problem better than the team that built the old one.
In practice, much of what looks like bad code is bad code that works. It contains fixes for edge cases that weren't obvious, workarounds for third-party behaviour that was never documented, and business logic that encoded decisions made years ago for reasons that are no longer visible in the codebase. None of this is apparent from reading the code. It becomes apparent when the new system encounters the same situations and fails.
The second reason rewrites fail is that the business doesn't stop while the engineering team rebuilds. New requirements arrive. The market changes. The product roadmap doesn't pause for a 12-month infrastructure project. Teams that commit to a full rewrite often find that by the time they're done, the new system needs to be rebuilt again — because the product has evolved faster than the rewrite could track.
The third reason is that the problems that made the old codebase difficult are usually not architectural — they're process. Teams that had poor code review culture, weak testing discipline, and insufficient architectural governance will produce the same kinds of debt in a new codebase. The rewrite doesn't fix the root cause.
When a Rewrite Is Genuinely Justified
That said, there are situations where incremental improvement isn't viable and a rewrite is the right answer.
The technology is end-of-life with no migration path. If a system is built on a framework or language that's no longer maintained, has known security vulnerabilities, and has no incremental upgrade path to a supported alternative — a rewrite may be the only option. This situation is more common in older systems and less common in products less than five years old.
The system is so poorly understood that nobody can safely change it. When a codebase has reached a state where no engineer on the team can confidently change any part of it without causing unexpected failures elsewhere — when there are no tests, no documentation, and insufficient institutional knowledge — the system has become more expensive to maintain than to replace. This is a genuine threshold, but it's reached less frequently than teams think. Adding tests incrementally, even to difficult code, is usually possible.
The business model has changed fundamentally. Occasionally a product evolves in a direction that is genuinely incompatible with its original architecture. A system built for single-tenant deployment that needs to become a multi-tenant SaaS platform, or a system built for batch processing that needs to become real-time, may have architectural constraints that can't be addressed incrementally. These situations require honest assessment of whether the architectural gap is truly fundamental or whether it's addressable through targeted refactoring.
The platform is changing. Moving from mobile-native to web, from on-premise to cloud, or from a single platform to a multi-platform product sometimes requires rebuilding because the underlying execution environment is different. This is different from refactoring — it's rebuilding for a different target.
The Middle Path That Usually Works
In most cases where a team believes they need a rewrite, what they actually need is an aggressive incremental improvement programme — with proper funding of engineering time and a clear plan.
The approach that works:
- Identify the highest-cost areas of the codebase — the areas that generate the most bugs, take the most time to change, or are on the critical path for the most features
- Write characterisation tests before touching anything — tests that capture the current behaviour so you can change the implementation without changing the outcomes
- Refactor module by module, replacing or improving the highest-impact areas first
- Apply the Boy Scout Rule consistently: leave every area you touch cleaner than you found it
- Invest in architecture governance to prevent new debt accumulating while you're addressing old debt
This approach is slower than a rewrite, but it carries a fraction of the risk. The system keeps running throughout. Users see no interruption. Progress is visible in each sprint rather than invisible for 12 months.
How to Tell Which Situation You're In
When you're inside a difficult codebase, it's hard to assess objectively. A few questions that help:
Can you safely make changes to the core systems? If your team is afraid to touch certain parts of the codebase because of the risk of unexpected failures, that fear is worth examining. If it stems from lack of tests, add tests. If it stems from genuine architectural entanglement that prevents safe incremental change, that's a stronger argument for something more drastic.
How long would adding tests to the most critical paths take? If the answer is "weeks" — that's a refactoring project, not a rewrite scenario. If the answer is "we can't, because the code is too entangled to test" — that's closer to rewrite territory.
How much of the business logic is in the codebase vs somewhere else? If significant business logic exists in external systems, spreadsheets, or undocumented process — a rewrite will miss it, and you'll spend months rediscovering edge cases.
What would the team do differently this time? If the answer is primarily architectural, the rewrite might address the root cause. If the answer is about process, code review culture, or discipline — those need to be fixed regardless, and a rewrite won't help.
If You Do Decide to Rewrite
A few principles that improve the odds:
Run the old and new systems in parallel until the new system has been proven against the same load and edge cases. Don't decommission the old system on a schedule — decommission it when the new one has earned it.
Scope the rewrite tightly. A rewrite of the authentication and billing subsystems is a very different risk profile from a rewrite of the entire product. Start with the smallest meaningful scope and validate the approach before committing to more.
Don't add features during the rewrite. Feature parity is the goal. New functionality can come after the migration is complete. Teams that try to add features during a rewrite almost always end up with neither a completed migration nor the features they wanted.
Accept that it will take longer than estimated. Rewrites almost always do. The risk management implication is to structure the project so that stopping midway and resuming the old system is always an option. If the business can only survive if the rewrite succeeds on schedule, the risk profile is already unacceptable.
If your engineering velocity has degraded to the point where you're considering a rewrite, the first step is usually a technical assessment — understanding whether the root cause is architectural or addressable incrementally. Our consulting team does exactly this kind of diagnosis. For teams that need senior engineers to execute a cleanup or migration programme, staff augmentation can provide the capacity. Get in touch.
We build the things we write about.
If you're working on something ambitious — AI systems, product builds, or scaling your team — let's talk.