A legacy modernization project has a distinctive way of failing. It does not blow up on day one. The plan looks sober, the budget looks reasonable, the milestones look defensible. Eighteen months in, the new system is still not in production, the old system is still running, the team has doubled in size, and the only honest answer to the question “when will we cut over?” is a long pause followed by a revised slide.
Most of these projects do not fail because the engineers were wrong about the technology. They fail because the project was structured as a big-bang replacement — build the new thing, prove it is identical to the old thing, cut over in one weekend — and the business kept moving while the rewrite was underway. By the time the replacement was ready, the old system had accumulated another thousand quiet changes, and the new system had to chase a target that never stopped moving.
The alternative is unglamorous, slower-feeling, and empirically more successful. It is the strangler-fig pattern, and this playbook is how we apply it in practice.
Why big-bang rewrites fail
Three forces conspire against the one-shot rewrite.
The first is undocumented behavior. Every legacy system has hundreds of small rules that nobody wrote down — a rounding quirk in invoice totals, a special case for one customer segment, a batch job that runs at three in the morning to fix yesterday’s edge cases. The team that builds the replacement does not know these rules exist until the new system is in front of users and an angry phone call reveals the missing behavior.
The second is the moving target. The legacy system is still in production while the replacement is being built. Finance adds a new report. Sales adds a new discount tier. Compliance adds a new data field. Every one of those changes now has to be made twice, and in practice the old system wins that race every time.
The third is the risk posture. A single cutover is the highest-risk event in any software program. If anything goes wrong, the business stops. That fear is rational, and it pushes cutover dates out indefinitely — each delay buys confidence at the cost of calendar, and eventually the project runs out of either budget or patience.
The strangler-fig pattern
The pattern is named after a plant that grows around an existing tree. The strangler fig does not cut the host down. It grows alongside it, slowly, until the host is hollowed out and can be removed quietly.
Applied to software, the shape is simple. You put a routing layer in front of the legacy system — usually an API gateway or a reverse proxy. Every request goes through it. For now, every request is routed to the legacy system. That is the starting state: nothing has changed functionally, but you now own the boundary.
Then you pick one piece of functionality — a single endpoint, a single screen, a single module — and you build the new version of it. When it is ready, you flip the router: that one piece of functionality now goes to the new system, everything else still goes to legacy. Users do not notice. The business does not pause.
Then you do it again. And again. Each increment shrinks the legacy footprint by a small amount. New functionality goes straight to the new system — the legacy codebase never grows again. Over time, the legacy system becomes a progressively smaller island, and eventually it is small enough that the final cutover is a weekend of work instead of a six-month project.
Deciding what to modernize first
The order matters. Modernize the wrong thing first and you burn two quarters without moving any meaningful business risk. We use three criteria, in roughly this priority.
Change frequency. What part of the legacy codebase do engineers touch every week? That part is where delivery velocity is lowest and frustration is highest. Moving that first produces the largest productivity gain per dollar invested, which funds the rest of the program politically.
Risk concentration. Is there a module that is holding the entire business hostage — an unmaintained queue processor, a fragile nightly batch, a piece of code whose original author left the company four years ago and whose logic is a mystery? Address those early, because they are the most likely to cause an unplanned outage.
Volume. If a component handles most of the traffic but is well-understood and stable, leave it for later. Counterintuitively, the biggest and scariest-looking modules are often the safest to modernize last.
The decision tree is roughly: high change frequency plus high risk first; low change frequency plus low risk last; the middle is negotiable and often dictated by which team has capacity.
Data migration, without a cutover weekend
Data is where most strangler-fig projects stumble. The application logic can be routed incrementally, but the data usually cannot be split as cleanly.
Three patterns cover most situations.
Dual-write means that when the new system writes data, it writes both to its own database and to the legacy database, keeping the two in sync. This is simple to reason about and lets the legacy reports and batch jobs keep working. The downside is consistency: if one write succeeds and the other fails, you now have a reconciliation problem. For low-volume, high-value data, dual-write is usually the right starting point.
Event sourcing bridge means the legacy system emits every change as an event, and the new system subscribes. The new system maintains its own read model, derived from the stream. This decouples the two systems in a way dual-write cannot, but it requires the legacy side to emit events reliably, which is sometimes impossible without invasive changes.
Final cutover is still needed at the end. After months of routing, some core data — usually historical records that nobody writes to anymore — has to be physically moved. By the time you get there, though, the volume is small, the shape is well understood, and the cutover window is short.
When to stop modernizing
This is the part most playbooks skip, and it matters.
A strangler-fig program does not have to finish. Somewhere around the eighty percent mark, the economics usually invert. The remaining twenty percent of the legacy system is often the part that works, has not changed in five years, handles a boring but important function, and costs almost nothing to keep running. Rewriting it delivers no business value — it just delivers architectural tidiness.
The honest answer, in most engagements we have run, is that the final ten to twenty percent of the legacy codebase should be left alone. Isolate it behind a stable API. Document what it does. Keep a runbook for when it eventually breaks. Spend the saved engineering capacity on something that actually moves the business.
A modernization program is not a demolition project. It is a risk-reduction program. When the risk is low enough, the program is done — even if the old system is still technically running somewhere in the corner of the rack.
What this looks like in practice
A typical engagement on this playbook follows a recognizable shape. The first quarter is almost entirely discovery and boundary-setting: put the routing layer in place, instrument traffic, map the legacy dependencies that nobody documented. No user-visible change happens yet.
The second quarter is the first slice. Usually a single high-frequency module is rebuilt and routed. This is the political moment of the program — the first time anyone non-technical sees that the approach works. If it ships, the rest of the program is much easier to fund.
From there, the cadence stabilizes. Every six to ten weeks, another module moves. The team gets faster as patterns solidify. Eighteen months in, most of the system is new. Two years in, what remains of the legacy is small enough to ignore or retire cheaply.
It is not as satisfying as a big reveal. There is no launch day, no “the new system is live” announcement. But the business kept running the whole time, the team never had to bet the company on a cutover weekend, and the modernization actually happened — which is more than most big-bang projects can claim.
Top comments (0)