In a rainforest, a strangler fig starts as a seed in the branches of a host tree. It grows roots downward and a lattice around the trunk, slowly taking over the host's structure. Years later the original tree has rotted away — and the fig stands exactly where it was, fully formed.
That's the perfect metaphor for replacing a legacy system. Instead of rewriting everything at once and flipping a switch, you grow the new system around the old one, moving over one piece at a time. The Strangler Fig pattern turns a high-risk rewrite into a long series of small, safe steps.
The problem
Big-bang rewrites are where ambitious projects go to die. You spend months or years building a replacement while the old system keeps changing underneath you. On launch day you cut over everything at once — and discover all the undocumented behavior, edge cases, and integrations the old system quietly handled. The blast radius is the entire product.
Meanwhile, you can't just stop and rebuild; the business needs the current system running the whole time. What you want is a way to modernize incrementally — perhaps peeling a monolith apart into microservices — while keeping the lights on and being able to back out of any single step that goes wrong.
Step through a year of replacing a four-route system below. Watch what users actually receive month by month, and predict what you can roll back on cutover day. Then flip the switch to replay the same year, with the same build dates, one route at a time.
How it works
You place a facade — often an API gateway or routing proxy — between callers and the system. At first, the facade simply forwards every request to the legacy application; nothing changes for the caller. Then you build the first replacement feature as new code and update the facade's routing: requests for that one feature now go to the new service, everything else still goes to the old one.
You repeat, feature by feature. Each step the new system grows and the legacy shrinks, while the facade hides the seam so consumers never know a migration is underway. The new code is free to use modern structure like a clean architecture without being dragged down by the old design. Eventually the legacy system handles nothing, and you delete it.
Step through a migration below. Each route is a chip that moves from the legacy side to the new side when one line of the facade's route table changes. When the new /orders breaks, predict the fastest safe fix before you see it.
Plan how the two sides share data. While both systems run, they often need the same data, and that's the trickiest part. You may need the new service to read the old database, sync changes between them, or route reads and writes carefully so neither side sees stale data. Decide this early — it's far more likely to bite you than the routing itself.
Before moving any feature, a team puts a facade in front of the legacy system that, for now, forwards every request unchanged. Why bother?
When to use it
Use the Strangler Fig whenever you're modernizing a system that's too important to take offline and too large to rewrite safely in one go — the classic monolith-to-services migration, or moving a creaky on-prem app to the cloud. Its great virtue is that you ship value continuously and can pause, reverse, or reprioritize at any point.
It's overkill for small systems where a clean rewrite is genuinely a weekend's work — the facade and dual-running overhead would cost more than they save. And it demands discipline: a half-finished migration that stalls leaves you maintaining two systems indefinitely. Commit to actually finishing the strangling, and the old tree really does come down.
Beware the last 20%. The first routes to move are usually the easy, well-understood ones. The ones left at the end are the gnarliest: odd batch jobs, reports nobody owns, behavior no one documented. Teams often stop there and keep both systems running for years. Track the legacy system's remaining routes down to zero, and budget for the tail from the start.
Halfway through a strangler migration, a customer updates their address on the new /profile page, but the legacy /account page still shows the old one. What's the most likely cause?