Refactor vs Rewrite: How to Modernize Legacy Software Safely
Refactor vs Rewrite: How to Modernize Legacy Software Safely
Refactor when the existing system still expresses the right business model and the pain is structural; rewrite when the model itself is wrong, the platform is unsupportable, or the cost of incremental change exceeds the cost of starting over with the original team's lessons in hand. In practice, the safe answer for most legacy systems is staged refactor behind a stable interface, with selective rewrites of isolated subsystems. Full rewrites are the exception, not the default.
The short answer
A rewrite restarts the clock on every bug you ever fixed. A refactor preserves that institutional knowledge. Unless the existing system is genuinely unsalvageable, refactor is lower risk, lower cost, and faster to value. The interesting question is not "rewrite or not," it is "which seams do we refactor behind, and which subsystems do we replace?"
When refactor is the right call
Refactor when:
- The domain model is roughly correct and the bugs are in execution, not in concept.
- The platform is supported and the dependencies are still maintained.
- The team that built it is partly still around, or the documentation is good enough to recover intent.
- The pain is concentrated in a few modules, not spread across the whole system.
- The product is generating revenue and you cannot afford a parallel build.
In those conditions, the legacy software modernization playbook is: stabilize the seams, add tests at the boundaries, then change one module at a time behind a stable interface. The system keeps running. Customers keep paying. Risk stays bounded.
When rewrite is the right call
Rewrite when:
- The platform is end of life and no upgrade path exists.
- The domain model is fundamentally wrong and every feature fights the schema.
- The codebase has no tests, no documentation, and no original authors, and the behavior cannot be recovered by reading the code.
- A regulatory or security requirement cannot be met inside the existing architecture.
- The cost of the next year of incremental change exceeds the cost of a parallel build plus a cutover.
Even then, the rewrite is rarely a clean-room project. It is usually a strangler-fig pattern: the new system absorbs responsibilities from the old one, route by route, until the old one can be retired.
The hidden cost of rewrites
Every rewrite the industry has publicly documented took longer than planned. The reason is consistent: the original system encodes thousands of small decisions that were made in response to real customer behavior, real edge cases, and real regulatory constraints. Most of those decisions are not written down. The rewrite team rediscovers them one production incident at a time.
A refactor preserves those decisions by construction. A rewrite has to relearn them.
The hidden cost of refactors
Refactors fail when the team underestimates the seams. If the existing code has no clean interfaces, every refactor pulls on a thread that touches the whole system. The fix is to invest first in the seams: extract interfaces, add boundary tests, isolate subsystems. Once the seams hold, refactor becomes safe and incremental. Skip that investment and the refactor becomes an unbudgeted rewrite in disguise.
A practical decision framework
Ask, in order:
- Is the domain model approximately right? If no, lean rewrite for the parts that are wrong.
- Is the platform supportable for at least three more years? If no, plan a platform migration, which may or may not require a rewrite of the application on top.
- Can we add tests at the seams without changing behavior? If yes, refactor is on the table. If no, fix that first.
- Is there a single subsystem that causes most of the pain? If yes, isolate and replace it. The rest can stay.
- What is the cost of being wrong? A failed refactor is a slow bleed. A failed rewrite is a missed year. Choose the failure mode you can afford.
The strangler-fig middle path
For most large legacy systems, the right answer is neither pure refactor nor pure rewrite. It is a strangler-fig migration:
- Put a stable interface in front of the legacy system.
- Build new functionality on the new platform behind that interface.
- Migrate existing functionality one slice at a time.
- Retire the legacy system when the last slice moves.
This pattern preserves revenue, bounds risk, and lets the team learn the new platform on real workloads before betting the company on it. It is the default we recommend in most engagements.
What we will not do
- Recommend a rewrite to justify a larger engagement. The economics of fixed-fee modernization are aligned against that.
- Refactor a system whose domain model is genuinely wrong. That is throwing good money after bad.
- Change the platform without changing the seams. The new platform inherits the old pain.
How to start
The first artifact in either direction is the same: a written assessment of the current system, the risks, the dependencies, and the seams. That is what a codebase review produces. With it, the refactor-versus-rewrite decision stops being a debate and becomes a calculation.
If the system is showing the symptoms in why AI-built apps break in production, the same logic applies. Most AI-built apps are refactor candidates, not rewrite candidates, because the domain model is usually fine and the pain is concentrated in the systems layer.
Need senior engineering leadership?
Engage a partner-led engineering firm that agrees on fixed fees, written scope, and accountability for outcomes instead of hours.
Access to semperMade's services is highly selective and subject to approval.