Why the "big rewrite" keeps failing
The instinct when a legacy system becomes unbearable is to freeze feature work, rebuild it properly from scratch, then cut over. It's an understandable instinct and it goes wrong constantly, for a consistent set of reasons: the rewrite takes far longer than estimated because the old system's edge cases were never written down anywhere except its own code; the business can't actually freeze feature requests for a year while engineering rebuilds; and the cutover itself is a single high-stakes event where every latent difference between old and new becomes a production incident at once.
None of this means legacy systems shouldn't be modernized. It means the rewrite-and-cutover approach is the wrong tool for systems that are still running the business while you fix them.
What the strangler fig pattern actually is
Named after the strangler fig vine, which grows around a host tree and gradually replaces it without the tree ever fully disappearing until the very end. Applied to software: instead of rebuilding the whole system and switching over at once, you put a routing layer in front of the legacy system and move traffic to new, properly-built components one piece at a time. The legacy system keeps running everything you haven't migrated yet. Nothing is ever fully "down" during the process, and if a newly migrated piece has a problem, you route that traffic back to the legacy system while you fix it.
You're not replacing the system. You're replacing pieces of it, one at a time, behind a routing layer that makes the transition invisible to everyone who depends on it — including your own team, most of the time.
How it actually plays out, phase by phase
- Map before you touch anything. Identify the system's real boundaries — not the ones in the original architecture diagram, the ones the code actually has today. This phase is where most of the "hidden requirements" the legacy system encodes get discovered, and skipping it is the single biggest cause of strangler fig projects going sideways later.
- Put a routing layer in front. A proxy or facade that can send a given request to either the legacy system or the new component, per-route or even per-customer. This is the piece that makes everything else safe — without it you're back to a big-bang cutover.
- Migrate the lowest-risk piece first. Not the most annoying piece, the lowest-risk one — something with clear boundaries and low blast radius if something's wrong. This validates the approach and the routing layer before anything customer-critical goes through it.
- Run old and new in parallel, and verify. Route a slice of real traffic to the new component and compare its output against the legacy system's for the same input before fully cutting over that route. This catches the edge cases the legacy system had quietly been handling that nobody remembered.
- Repeat, and keep the rollback path live. Each migrated piece should be routable back to legacy in minutes, not hours, for as long as there's any doubt — this is what turns "high-stakes cutover" into "low-stakes, reversible change."
- Decommission legacy only when nothing routes to it anymore. Not before. The old system earns its retirement by having genuinely nothing left depending on it, verified by the routing layer's own logs, not by assumption.
Where teams get it wrong
Two mistakes account for most strangler fig projects that stall out. The first is skipping the mapping phase and migrating whatever seems easiest, which usually means discovering a critical undocumented behavior three pieces in in production, not two. The second is treating the routing layer as a temporary hack rather than real infrastructure — it needs the same monitoring, testing, and on-call attention as anything else in the request path, because for the duration of the migration, it is the request path.
Have a legacy system nobody wants to touch?
We'll map what it actually does — not what the documentation says — before proposing anything.
Frequently asked questions
How long does a strangler fig migration take compared to a rewrite?
Longer end-to-end in most cases — but the business keeps getting value and keeps running throughout, instead of betting everything on a single cutover date. The comparison that matters isn't total calendar time, it's risk and continuity.
Does this work for a system with no tests and no documentation?
It's often the better option specifically because of that — the parallel-run-and-verify step (routing real traffic to both old and new, comparing outputs) becomes your test suite, discovering the undocumented behavior as you go rather than assuming you already know it.
What if the legacy system's database schema is the actual problem?
The pattern still applies — the routing layer and the migration phases can operate at the data layer too, not just the request layer, migrating data ownership piece by piece with the same parallel-run safety net.
Is this pattern only for large enterprise systems?
No — the principle (incremental replacement behind a routing layer, verified before cutover) scales down to a single problematic module in a smaller codebase just as well as it scales up to a monolith.