The most dangerous sentence in software ownership might be “we should just rewrite it.” It gets said about systems that deserve it — fifteen years old, undocumented, held together by the two people who remember why the invoice module behaves that way. It also gets said about systems that are load-bearing: orders, stock, billing and payroll flow through them every day, and the business stops when they do. The rewrite promise is seductive because it is clean. A new system, built properly this time, and one day you flip the switch. The record is less clean: big-bang rewrites of working systems fail often enough, and expensively enough, that the phrase should be treated as a claim requiring evidence rather than a plan.
There is an alternative with a far better completion rate: incremental replacement — the strangler-fig pattern, named after the fig that grows around a host tree until the tree is no longer holding anything up. The new system grows around the old one, takes over one module at a time, and the old system is retired when nothing routes to it any more. It is less dramatic than a rewrite. That is the point.
Why big rewrites fail
The failure modes are predictable enough to have names. The first is the second-system effect: the team rebuilding a system carries into the rebuild every ambition the old one suppressed. A project scoped as “what the old system does, on a modern stack” quietly becomes “what the old system does, plus the reporting sales always wanted, plus a plugin architecture, plus multi-tenancy in case we ever need it.” Scope grows in the one project where scope was supposed to be fixed, because the old system sits there as a daily reminder of everything it never did.
The second is the frozen-feature window. A rewrite only stays a rewrite if the target holds still, so the old system is frozen while the new one is built. But the business does not freeze. Eighteen months is a long time to tell sales there is no new anything, and almost no company holds that line — so changes go into the old system anyway, the rewrite chases a moving target, and the gap between the two widens at precisely the moment it was supposed to be closing.
The third is the data nobody understands. A system that has run a business for a decade is not described by its documentation; it is described by its behaviour. The odd rounding in the discount calculation, the status code that exists for one customer from 2016, the nightly job that repairs what the day shift breaks — none of that lives in a spec, because the old system is the spec. A big-bang rewrite discovers these requirements at the worst possible moment: after cutover, in production, as support tickets. That discovery is usually what kills the project.
What incremental replacement actually looks like
The strangler pattern has a small number of moving parts. First, a facade goes in front of the old system — a routing layer that receives every request and, on day one, passes all of them straight through. Nothing changes except that you now own the front door. That is the whole first milestone, and it is worth more than it looks: from this point on, where any piece of functionality lives is a routing decision you control, invisible to the people using the system. The facade can be an API gateway, a reverse proxy or a thin service of your own — the technology matters far less than the fact of it.
Then replacement proceeds one module at a time. A bounded piece of the system — quoting, say — is rebuilt as a new service on a deliberately boring stack, because a modernisation is the last place to spend your novelty budget. The new module runs in parallel with the old one: both receive the same inputs, both produce outputs, and the outputs are compared. The old module’s answer is the one customers see; the new module’s answer is a shadow, logged and diffed.
Cutover happens per module, and only when the numbers match — not on the roadmap date, not when the demo looks right, but when the diff report has been clean, or explainably non-clean, for long enough to trust. Then the facade routes quoting to the new module, the old one stays running as a fallback, and attention moves to the next module. Every step is small, measured and reversible — which is exactly what a big-bang cutover is not.
The old system is not the enemy. It is the spec, the test oracle and the fallback — right up until the day nothing routes to it any more.
How to pick the first module
The first module carries a disproportionate share of the project’s fate, because its real job is to prove the machinery — the facade, the parallel run, the reconciliation, the cutover — on something that cannot sink the company. Three filters do most of the work. Low coupling: pick something that shares the fewest tables and calls with the rest of the system — document generation, notifications, a reporting flow — so you are not untangling the whole knot on day one. High pain: pick something people actively complain about, so the first cutover produces a result somebody outside the project notices. Often the best candidate is the workflow the team already patches with exports and re-keying — the spreadsheet layer around a legacy system is a map of where it hurts. Measurable: pick something whose output can be compared mechanically — documents generated, totals computed, response times — because the whole cutover discipline depends on having numbers to match.
What the first module should not be is impressive. Small, annoying and countable beats large and strategic. A first module that takes six weeks and cuts over cleanly buys more credibility than one that takes six months and might. The strategic modules come later, once the machinery has survived contact with production.
The data problem
Code is the easy half of a modernisation. The hard half is data, and the ruling principle is blunt: the old system remains the source of truth until the new one has proven otherwise, module by module. In practice that means data flows one way while a module is in shadow — synced from old to new by change-data-capture if the old database supports it, by nightly batch if that is what the platform allows. The new module reads its synced copy, computes its answers and writes nothing back that anyone depends on.
Between shadow and cutover sits the reconciliation period, and it is where the real requirements surface. Both systems process the same day’s work; a diff job compares the results; every mismatch gets investigated. Most mismatches are the new system being wrong in ways no spec would have caught — the undocumented rounding, the special-cased customer. Investigated one at a time, in shadow, they are cheap lessons. The same discoveries after a big-bang cutover are outages.
Some mismatches point the other way: the old system was wrong, and the business has been quietly absorbing the error for years. Those are decisions, not bugs — preserve the old behaviour for continuity, or fix it and tell the people affected? Either answer can be right; what is wrong is making the choice by accident, which is what every rewrite that never ran the two systems side by side has done. Only at cutover does ownership of the module’s data transfer — the new system becomes the writer, and the reconciliation job keeps running for a while afterwards as a tripwire.
When a rewrite is the right call
Honesty requires the counter-case, because incremental replacement is not free — the facade, the sync, the parallel running all cost effort a clean rewrite would skip. That overhead is insurance, and insurance is not worth buying on every house. If the system is small enough that a focused replacement ships in 2–3 months, the coordination machinery can cost more than the risk it removes. If the platform is genuinely dead — a runtime nobody can hire for, hardware that cannot be replaced, a vendor that no longer exists — there may be nothing stable left to strangle. And if the system has few users who can tolerate a hard switch — an internal tool used by a team you can walk over and talk to — parallel running is caution the situation does not demand.
Even then, keep two habits from the incremental playbook: migrate the data before the deadline rather than during it, and run old and new side by side for a validation period if there is any way to do so. The rewrite-versus-strangler decision sets the size of the safety apparatus — it does not change whether outputs get verified before anyone depends on them.
The political side
A modernisation that takes a year is a political project, whatever the architecture. Two systems running side by side means paying for both, and at some point a reasonable person will ask the question every long project dreads: is this ever finishing? The strangler pattern’s most underrated feature is that it generates its own answer. Because replacement is per-module, progress is countable — modules cut over, share of traffic served by the new system, incidents per cutover. The project has a scoreboard, not a percentage-complete estimate that sat at eighty for a year.
The cadence that keeps trust is the weekly demo of something real: not slides, but the new quoting module producing this week’s actual quotes next to the old one’s, with the diff on screen. Stakeholders watch the mismatches shrink week over week, which does more for confidence than any plan ever has. And commitments stay small on purpose: never promise the date the old system dies — promise the next module, deliver it, repeat. That cadence is how we run legacy modernisation engagements, and it is the difference between a modernisation the business believes in and one it merely tolerates.
The old system is usually spoken of as the problem. Treated correctly, it is the opposite: a complete, running specification of what the business needs, a test oracle that grades the replacement’s every answer, and a fallback that makes each step reversible. The big rewrite throws those assets away and then fails for lack of them. Incremental replacement spends them, deliberately, module by module — until the day someone checks the routing table, finds nothing pointing at the old system, and turns it off. No drama. That was the point.