All articles

Replacing a legacy system without a big-bang rewrite

Why incremental cutovers fail on the org chart before they fail on the code — and how to sequence a replacement so the business keeps running throughout.

Every company with a system that has run for a decade eventually hears the same proposal: freeze the roadmap, rebuild from scratch, and switch over on a weekend. It is almost always the wrong plan. The rebuild takes twice as long as promised, the business changes underneath it, and the "switch over on a weekend" turns into a board-level incident. The alternative is older and less glamorous — and it is the one that actually works.

The rewrite fails on the org chart first

A big-bang rewrite asks the business to stand still while engineering catches up to where it already was. But the market does not pause, regulations change, a competitor ships, and the sales team promises a feature that only exists in the old system. So the old system keeps getting changed — which means the new one is now chasing a moving target. Two teams drift apart, the "freeze" quietly thaws, and eighteen months in you are maintaining two systems instead of one.

The failure is rarely technical. It is that a rewrite couples the entire business to a single, distant cutover date, and nothing about a real business tolerates a single distant date.

What the strangler fig actually is

The pattern takes its name from the fig that grows around a host tree, gradually taking over its structure until the original is gone — but the canopy never stops functioning. In software it means: put a routing layer in front of the legacy system, then replace it one capability at a time. Each new slice runs in production next to the old one; the router decides, per request, which implementation serves it. When a slice is proven, you route all its traffic to the new code and delete the old path. The system is never rewritten. It is gradually replaced while fully live.

The mechanics are unremarkable, which is the point: a façade or gateway that owns routing, a new service for the extracted capability, and a deliberate decision about who owns the data that capability touches. The discipline is in the sequencing, not the code.

Sequence by risk, not by elegance

The instinct is to start with the ugliest part of the codebase. Resist it. The first slice should be chosen to retire risk and prove the approach, not to satisfy an engineer's sense of tidiness. Good first candidates are capabilities that are well-understood, have clear boundaries, carry real business value, and are painful enough that success is visible to the people approving the budget.

We sequence around three questions. Where is the business exposed today — the slice that breaks at 2am? Where is change most frequent, so a cleaner implementation pays off fastest? And where are the data boundaries clean enough that we can move a capability without dragging half the schema with it? The intersection of those three is the first slice.

Data is the hard part, not the code

Extracting behaviour is straightforward. Extracting the data that behaviour depends on is where migrations stall. A capability that reads and writes tables shared by ten other features cannot simply be lifted out. You have a few honest options, and each has a cost. You can keep the data in place and have the new service reach back into the legacy database through a narrow, explicit interface — fast to build, but it postpones the real separation. You can dual-write to both the old and new stores during a transition and reconcile continuously — safer, but you carry two sources of truth for a while. Or you can make the new service the owner and have the legacy system read from it — clean, but it means the legacy code has to change too.

There is no universally correct choice; there is only the one that fits the slice. What matters is that the decision is made deliberately and written down, because an undocumented dual-write is how a modernization quietly becomes a data-integrity project.

Knowing it is working — and when it stalls

A healthy strangler migration produces something to inspect every few weeks: a slice in production, a measurable reduction in incidents on the old path, a clear line on the map moving from "legacy" to "replaced." If months pass with nothing shipped, the problem is usually not engineering. It is that the routing layer has become a place to add features instead of a place to remove them, or that no one has been willing to delete the old code once a slice is proven. Deleting the old path is not cleanup you do later — it is the step that makes the migration real. Until the legacy code is gone, you are running two systems and paying for both.

A short checklist before you start

Before committing to a strangler approach, get honest answers to a few questions. Can you put a routing layer in front of the system without a month of yak-shaving? Do you know, for the first slice, exactly which tables it owns and which it merely borrows? Is there a person on the business side who will notice and care when the first slice ships? And — the one people skip — is everyone agreed that "done" means the old code is deleted, not merely bypassed? If those answers are yes, incremental replacement will almost always beat the rewrite, because it keeps the thing that matters most running the entire time: the business.

Have a system like the ones we write about?

We start most engagements with a two-week audit. It ends in a plan you can fund.

Book a discovery call