Introduction
The Strangler Fig Pattern replaces a legacy system incrementally by inserting an interception layer (a reverse proxy or API facade) in front of it and re-pointing one capability's route at a time to a new service, so the monolith's share of live traffic shrinks toward zero without a single big-bang cutover.
The name comes from the strangler fig tree, which germinates high on a host tree, sends roots down around the trunk, and slowly envelops it until the original tree dies and rots away, leaving a self-supporting fig in its exact shape. That is the whole strategy in one image: you grow the new system around the old one, feeding on its traffic, until the old one can be safely removed. The mechanism that makes this possible in software is not the tree — it is the facade in front that can send each request to either backend.
How the facade routes one request
The facade is the only thing clients talk to; it holds a routing table and forwards each request to whichever backend currently owns that path. Trace two live requests during the migration above:
GET /orders/history?userId=8821arrives at the facade.- The facade matches the longest-prefix rule
/orders/historyand its target isorder-history-svc(new-service weight = 100%). - It forwards the request; the new service queries the
orderstable and returns200in ~180 ms. - Moments later
GET /cart/8821arrives. No new-service rule matches, so the facade falls through to its default target — the monolith — which answers as it always has. - The client saw a single hostname and never knew two different systems answered. That indistinguishability is exactly what lets you move routes one at a time.
A worked migration, week by week
Concrete scenario: an e-commerce monolith serving /orders, /cart, /payments, and /catalog. We strangle the single endpoint GET /orders/history first. The whole point is that every phase is a config change at the facade, reversible in seconds.
| Phase | Facade rule for /orders/history | Traffic to new svc | What you verify before proceeding |
|---|---|---|---|
| Wk 0 — baseline | none (all → monolith) | 0% | Proxy inserted in front of everything; p99 unchanged except the ~2 ms extra hop. |
| Wk 1 — shadow | mirror (legacy still answers) | 0% served, 100% copied | New svc gets a copy of every request; diff its response against legacy. Found 3 mismatches in date formatting — fix them here, with zero user impact. |
| Wk 2 — canary | split | 5% | New svc error rate 0.02% ≈ legacy; p99 180 ms vs legacy's 210 ms. |
| Wk 3 — ramp | split | 50% | No rise in 5xx; extra read load on the shared DB is within budget. |
| Wk 4 — cutover | route | 100% | The legacy /orders/history code path receives 0 requests for 7 days. |
| Wk 5 — kill | route | 100% | Delete OrderHistoryController from the monolith. The monolith is now measurably smaller. Repeat for the next capability. |
If any phase regresses, you flip one weight back to 0% and you are instantly on the old system — no rollback deploy, no restore. That reversibility is the pattern's core safety property. One honest qualifier: this instant reversibility is a property of the routing, not the data. It holds fully while the migrated slice is read-only (like /orders/history here) or while both backends share one store. The moment a write route is canaried to a service that owns its own store, flipping the weight back strands every row the new service wrote during the canary — a real rollback plan for a write path needs a data story too (replay the new store's writes into legacy, or keep a sync running both ways during the window). The Key Insights page traces this failure end to end.
The rollback that isn't (write canary at 5%)
| t | What happens |
|---|---|
| t0 | POST /orders #101 routed to the new service; committed in the new store |
| t1 | Canary regression detected; facade weight flipped back to 0% |
| t2 | GET /orders/history now served by the monolith from legacydb — order #101 is missing, and the customer paid for it |
| t3 | Recovery = replay the new store's canary-window writes into legacydb (idempotent upsert), then declare the rollback complete |
Flip the weight, then reconcile the window — in that order, always.
Pitfalls
- The eternal strangler. Teams migrate the easy 80% and abandon the gnarly 20% (the batch jobs, the reporting exports). The monolith never dies, and now you run three things: the monolith, the new services, and the facade. Budget explicitly for finishing the tail, or you land in the worst state, not the best.
- The shared-database trap. The naive version points the new service at the monolith's tables (the amber dashed line in the diagram). Nothing is truly decoupled: a schema migration can break both, and you cannot deploy or scale them independently — you have built a distributed monolith. Splitting the data (via dual writes or change-data-capture) is usually the hard 20%, not the routing.
- Behavioral drift. Clients depend on the legacy system's quirks — even its bugs (a specific null handling, a field ordering). If the new service is "more correct," it breaks callers. The Wk-1 shadow/parallel-run phase exists precisely to catch this before users do.
- Sticky session state. If the monolith keeps session in process memory and relies on sticky routing, splitting a fraction of requests to a new service breaks session continuity mid-flow. Externalize session state (shared cache/token) before you canary.
- Facade as a single point of failure. Every request now traverses one proxy. If it is not run highly-available, you have concentrated all your risk into the very component you added for safety.
- Moving target. The business keeps shipping features into the monolith while you migrate. Agree on a feature freeze for capabilities in flight, or you will chase a spec that changes under you.
When to use it — and when not to
Reach for Strangler Fig when the system is (a) large and business-critical, so downtime and big-bang risk are unacceptable; (b) decomposable into capabilities behind identifiable seams — an HTTP boundary, a message queue, a set of URL prefixes you can route on; and (c) fronted by an interception point where you can insert a facade. These three signals together are what make incremental replacement possible.
Do not use it when the system is small or short-lived — the proxy scaffolding, dual maintenance, and multi-month coordination cost more than the thing you are replacing. Also skip it when there is no clean seam: if everything is coupled through shared in-process state you cannot intercept at a boundary, there is nothing to route.
Trade-offs vs the alternatives
- vs. Big-Bang Rewrite. Big-bang gives you a clean slate with no proxy and no period of dual maintenance — but you pay with enormous cutover risk, a long stretch before any value ships, and a feature freeze on the old system while you rebuild (the classic "second-system" death march). Strangler trades that for slow, safe, always-reversible progress at the cost of running two systems plus a facade for months. Choose big-bang only when the system is small/disposable or the old one literally can no longer run; otherwise strangle.
- vs. Branch by Abstraction. Branch by Abstraction inserts the seam in-process — an abstraction layer inside one deployable, behind which you swap implementations — so there is no network hop, no added latency, and simpler ops. But because both implementations ship in the same binary, you cannot deploy or scale them independently, and you are still releasing one monolith. Choose Branch by Abstraction when the replacement stays in-process (a library or module swap); choose Strangler Fig when you are crossing a process/service boundary.
Crisp rule: choose Strangler Fig to migrate a live, critical system across a network boundary one capability at a time; prefer a rewrite for small/throwaway systems; prefer Branch by Abstraction when the seam never leaves the process.
Takeaways
- The pattern is not the tree — it is the facade in front that can route each request to either the old or the new backend, letting you migrate one capability at a time.
- Every migration step is a facade config change (shadow → canary → ramp → cutover → delete), so rollback is a weight flip, not a redeploy. That reversibility is the entire safety argument — for read routes; write routes additionally need a data-reconciliation plan before you can call the flip a rollback.
- The routing is the easy part; splitting the shared data and killing the last 20% of the legacy is where these projects actually stall — plan and fund the tail up front.
- Prefer it for large, live, seam-able systems; a rewrite for small ones; Branch by Abstraction when the seam is in-process.
Re-authored and deepened for this guide. Draws on Martin Fowler's "StranglerFigApplication" and "BranchByAbstraction" (martinfowler.com), Sam Newman, Monolith to Microservices (O'Reilly, 2019) — the shadow/parallel-run, canary, and data-decomposition guidance — and Chris Richardson's microservices.io pattern catalog. The strangler fig metaphor originates with Fowler's observation of the trees in Queensland, Australia.
🤖 Don't fully get this? Learn it with Claude
Stuck on Introduction? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.
Build the mental picture, not memorization.
I just read a lesson on **Introduction** (System Design) and want to truly understand it. Explain Introduction from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Socratic — adapts to where you're stuck.
Teach me **Introduction** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Active recall exposes what you missed.
Quiz me on **Introduction** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Intuition + hook + flashcards for long-term memory.
Help me remember **Introduction** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.