Why most legacy rewrites fail (and what to do instead)

Isometric illustration: a vast half-finished structure collapses into dark water while beside it two figures extend a smaller intact structure one glowing block at a time

Almost every legacy software modernization project starts with the same sentence in a planning meeting: “It would be faster to just rebuild it.” The old system is slow to change, nobody enjoys touching it, and a clean codebase written by the people in the room sounds like relief. The instinct is honest. It is also, in our experience, the most expensive wrong turn a technical organisation can take.

Rewrites do not usually fail loudly. They fail slowly – the new system trails the old one for months, the “temporary” period of running both stretches into a year, and the clean slate arrives carrying the same complexity it was meant to escape. This article is about why that pattern is so reliable, and the modernisation strategies that avoid it. If you are picturing a from-scratch rebuild, read this before you commit the budget.


The second-system effect explains the pull, not the outcome

The desire to rewrite is not a character flaw. Fred Brooks named the mechanism behind it in The Mythical Man-Month: the second-system effect. When an engineer who has lived with the constraints of a first system finally gets to design its replacement, they over-build it. Every feature caution made them cut the first time goes back in. Every awkward compromise gets a general-purpose abstraction. The second system is the most dangerous a person ever designs, precisely because it is built with confidence and no fresh scars.

A rewrite is the second-system effect at organisational scale. The team knows everything the old system got wrong and almost nothing about why. Joel Spolsky put the conclusion bluntly two decades ago: rewriting a working codebase from scratch is “the single worst strategic mistake” a software company can make, because it throws away the one asset that is genuinely hard to reproduce – a system that already survives contact with reality every day.

A rewrite never starts from zero. It starts from behind – competing against a system that already works, on the day you have written no code at all.


Why rewrites fail: three mechanisms, every time

Rewrites fail for three reasons that compound each other: the complexity is underestimated, the institutional knowledge is lost, and the old system still has to be maintained while the new one is built. Any one of these can sink a project. Together they are why a rebuild that looked like a six-month job is still not in production eighteen months later.

The complexity is real and mostly invisible

A legacy system looks simple from the outside because most of its complexity is buried in behaviour, not structure. The gnarly conditional that handles a currency rounding case for one country, the retry that exists because a downstream partner drops connections at midnight, the validation nobody can explain but that breaks billing when removed – none of this is visible in an architecture diagram. It only lives in the code.

When you estimate a rewrite, you estimate the visible system – the features you can list. The edge cases are invisible until the new system meets production traffic and starts getting them wrong. Each one then has to be rediscovered, usually by a customer, usually as an incident. The old system did not get those cases right because it was well designed. It got them right because it had already been wrong once and someone fixed it.

The ghost spec

A running legacy system is the most accurate specification of your business rules that exists anywhere. Every surviving edge case is a requirement someone genuinely needed, added in response to a real failure, and never wrote down. Because that specification is unwritten, a from-scratch rewrite cannot read it – it can only rediscover each rule the hard way, in production, one incident at a time. The rewrite does not decline to copy the ghost spec. It silently discards it, then pays to relearn it.

The knowledge walks out with the code

The people who understood why the system works the way it does are frequently gone, and the reasoning left with them. What remains is the code, a record of every decision even when the rationale is lost. Delete it and start fresh, and you are not clearing away confusion – you are destroying the last surviving copy of a decade of hard-won judgement.

This is why the hardest part of any serious modernisation is archaeology, not architecture. Before a line of new code is worth writing, someone has to recover what the current behaviour actually is and which parts of it are load-bearing. We wrote a whole guide on doing that when the documentation is gone: where to start when nobody wrote the docs.

You are now paying for two systems

The old system does not politely freeze while you rebuild it. Customers still report bugs, regulators still change rules, and the business still needs new features shipped this quarter. So the team splits: some keep the legacy system alive, some build its replacement, and the two groups drift apart on what “correct” even means. Every feature added to the old system during the rebuild is a feature the new one now also has to grow, which is how the finish line keeps moving.

This parallel-maintenance burden is the cost build cases almost never model honestly. It is not a transition tax you pay for a few weeks at the end; it is a double running cost you carry for the entire project, and the longer the rewrite takes, the more of it you pay.


Three legacy software modernization strategies that actually ship

The alternative to a rewrite is not “live with the mess forever.” It is to modernise incrementally, so that value ships continuously and the old system is never fully offline. Three patterns cover most situations, and the right one depends on why the current system hurts.

Strangler fig: replace piece by piece behind a facade

Martin Fowler’s strangler fig pattern puts a routing layer in front of the legacy system and moves capabilities across it one at a time. Each new component takes over a slice of traffic while the rest keeps flowing to the old code, so the system is always live and every step is independently reversible. The legacy application shrinks until, eventually, nothing routes to it. We have run this against a live Java monolith and written up exactly how the routing and cutover worked: replacing a live Java monolith with the strangler fig.

Modular decomposition: extract one bounded context at a time

Where the strangler fig replaces behaviour behind a facade, modular decomposition carves the system along its natural seams, billing, identity, inventory, and lifts each bounded context out as a service you can own and deploy on its own. It suits systems whose problem is entanglement rather than obsolete technology: the logic is fine, but everything is welded to everything else. Get the seams wrong and you simply distribute the tangle, so the analysis matters more than the code.

Wrap-and-extend: a modern layer over untouched internals

Sometimes the legacy core is not the problem – the problem is that nothing modern can reach it. Wrap-and-extend puts a clean API layer over the existing internals and builds all new work against that layer, leaving the core untouched. It is the fastest way to unblock a team, and it is exactly what we did for Dango, where the leverage came from decoupling the reporting layer rather than touching the source systems underneath.

How to choose among the three incremental strategies.

StrategyBest whenWatch out
Strangler figThe technology is genuinely dated and you want to replace behaviour gradually while staying liveYou need a reliable routing layer and discipline to actually retire the old paths, not leave both running forever
Modular decompositionThe logic is sound but everything is entangled, and teams keep blocking each other in one codebaseBad seam choices distribute the mess instead of removing it; the domain analysis is the hard part
Wrap-and-extendThe core works and is trusted, but nothing modern can integrate with it and you need to move nowThe legacy debt still exists underneath; this buys time and reach, not a clean core

These are not mutually exclusive. A real programme often wraps first to unblock the team, then strangles the parts that most need replacing. What they share is the property a rewrite lacks: at every step the system is running and delivering, and you can stop whenever the value runs out. Our legacy modernisation work is built almost entirely on this principle.


When a rewrite is genuinely justified

Sometimes the right answer really is to start over, and pretending otherwise would be dishonest. A full rewrite earns its risk in a narrow set of cases. If one of these describes you, stop reading and go plan the rebuild – the incremental patterns above will only slow you down.

  • The platform is end-of-life with no migration path. The language, runtime, or database is unsupported, unpatchable, and cannot be incrementally moved. You are not refactoring; you are evacuating.
  • The business model changed underneath the architecture. The old system was built for a company that no longer exists, a different pricing model, a different unit of work, and no seam in it maps to what you now sell.
  • The system is genuinely small. If you can hold the whole thing in your head and rebuild it in weeks, the ceremony of an incremental migration costs more than it saves.

The only time a rewrite is justified

A rewrite is justified when the old system’s behaviour is something you want to escape, not preserve. If you still need most of what it does today, you need to migrate that behaviour, not rebuild around it – and migration is what the incremental patterns are for.

Most teams tempted by a rewrite do not fall into these cases. They have a system that works and is inconvenient, which is a different and far more common problem. If you are unsure which side of the line you are on, we mapped the decision in detail in rewrite vs refactor: when each is right.


Proof: the best modernisation often leaves the legacy alone

The highest-leverage modernisation we have shipped repeatedly avoided touching the legacy source systems at all. Mediacom’s analysts were losing four days of every week to a weekly report stitched by hand from three siloed data sources – no shared schema, no API, no source of truth, no audit trail. The obvious pitch was to rebuild those source systems. We did not.

Instead we decoupled the reporting layer and encoded the reconciliation rules in software above the untouched legacy platform. Twelve weeks later the same report ran in one day instead of four, a 75% reduction in reporting time, returning roughly 120 analyst hours a month, with zero disruption to the production systems underneath. The leverage was in the wrap, not a rewrite. The full Dango case study has the detail.

The same principle holds over the long run. Our mScreen partnership with GroupM has run since 2018, growing to every GroupM agency across more than 78 markets, and the encoded methodology at its core has never needed a full rebuild. Systems extended carefully outlast the ones torn down and started again – that is not luck, it is the compounding value of never discarding the ghost spec.


Reframe the question before you commit the budget

The rewrite question is almost always the wrong question. “Should we rebuild this?” invites a clean-slate fantasy. The better question is “which behaviour do we need to keep, and what is the smallest change that reaches it?” – and that question points straight at incremental legacy software modernization, not a rebuild. Most of the time, the answer ships value in weeks and leaves the risky rewrite where it belongs: unbuilt.

If you are staring down a system everyone wants to rebuild and you are not sure that is the right call, that is exactly the conversation worth having before the budget is committed. Tell us what the system does and what hurts about it – the answer usually decides itself.