The strangler fig pattern, explained with a SaaS migration
The strangler fig pattern is a way to replace a legacy system gradually: you put a routing layer in front of it, build new functionality alongside it, and move traffic over one slice at a time until the old system handles nothing and can be switched off. At every step, both systems run in production and the product keeps shipping.
Martin Fowler named it in 2004 after the strangler figs he saw in Queensland’s rainforest, which grow around a host tree until the tree is no longer needed. It’s the approach I reach for first whenever a team tells me they need a rewrite. Below is how it works in practice, with the details most explanations skip.
Why the strangler pattern beats a big-bang rewrite
A big-bang rewrite asks you to freeze the old system, rebuild everything it does (including the behavior nobody documented), and cut over in one risky weekend. It delivers no value until the very end, and the end keeps moving. I’ve written about why in rewrite vs refactor.
The strangler pattern inverts that. Each slice you migrate is in production within weeks, so you find out early whether the new architecture actually works. If priorities change halfway through, you stop with a smaller legacy system and a working new one, rather than two half-finished products.
A realistic SaaS example
Take a common case: a B2B SaaS running a ten-year-old PHP monolith. It handles marketing pages, signup, the core app, billing, reporting and a public API. The team wants to move to a modern framework, and billing is the most painful area – it’s where incidents cluster and where pricing changes take weeks.
A strangler migration for that product might run like this:
- Put a proxy in front of everything. Nginx, Envoy, a CDN like Cloudflare, or an API gateway. On day one it forwards 100 percent of traffic to the monolith. Nothing visible changes, but you now control routing.
- Pick the first slice. Not billing. Choose something low-risk with clear boundaries – the reporting pages, say, which mostly read data. You want to prove the deploy pipeline, auth sharing and observability on something where a bug isn’t a revenue event.
- Build the slice in the new app and route by path. /reports/* goes to the new service; everything else still goes to the monolith. Use a feature flag or header rule so you can route internal users first, then a percentage of customers, then everyone.
- Delete the old code. Once the new slice has run cleanly for a few weeks, remove the old implementation from the monolith. This step is what makes it a migration rather than a duplicate.
- Repeat, moving toward the core. Signup and account settings next, then the public API behind a versioned path, and billing once the team has done it four or five times.
- Decommission. When the proxy sends nothing to the monolith, turn it off, archive the repo, and remove the routing rules that pointed at it.
The routing layer: what it needs to do
The proxy is the heart of the pattern, and it needs a few capabilities from the start:
- Routing by path, header, cookie or user. Path is enough for most web apps; header or user-based routing lets you canary a slice to internal accounts first.
- Shared session and auth. Both apps have to recognize the same logged-in user. The simplest route is a shared session store or a signed token both sides can verify. Plan this before slice one, because retrofitting it is painful.
- Consistent URLs. Customers and integrations shouldn’t notice which system served a request. Keep paths, redirects and cookies identical.
- Observability across both. Propagate a request ID through the proxy and both apps so you can trace a single request regardless of where it landed.
- Instant rollback. Moving a route back to the monolith should be a config change measured in seconds, not a deploy.
For backend-to-backend calls, the equivalent of the proxy is an interface inside the monolith (branch by abstraction) that can call either the old code path or the new service.
Data ownership is the hard part
Routing HTTP requests is easy. Deciding who owns the data is where strangler migrations stall. The common options, from simplest to cleanest:
- Shared database. The new app reads and writes the monolith’s tables directly. Fast to start, and fine for early read-only slices, but it couples both systems to the same schema, so neither can change it freely. Treat it as temporary.
- New system owns its tables, monolith reads through an API. The cleaner end state. When billing moves, billing data moves with it, and the monolith asks the billing service for what it needs.
- Synchronization during the transition. While both systems write the same concept, you need one to be the source of truth and the other to receive changes – via dual writes behind a flag, change data capture (Debezium on the database log, for example), or domain events published through an outbox. Pick one direction and stick to it; bidirectional sync is a long-running source of bugs.
My rule of thumb: a slice isn’t finished until its data has a single owner. Migrating the code but leaving the tables shared just moves the coupling somewhere harder to see.
Pitfalls that stall a strangler migration
- The last 20 percent never moves. Teams migrate the easy slices, momentum fades, and the company runs two systems indefinitely – the worst of both. Set a decommission date and track the share of traffic still hitting legacy.
- Not deleting old code. If the old path stays “just in case”, people keep patching it and you’re maintaining both.
- Starting with the riskiest slice. Billing first sounds brave. It usually means learning the migration mechanics on the code where mistakes cost real money.
- Rebuilding bugs as features. Legacy behavior that customers depend on is a contract. Capture it with characterization tests or by replaying production traffic against both systems and diffing the responses.
- Two teams, two worlds. A “new platform team” and a “legacy team” creates status games and knowledge silos. The same people should own a slice end to end, old and new.
- Cross-cutting concerns left until last. Auth, permissions, audit logs, tenancy scoping and emails touch every slice. Decide early where they live.
How to know when you’re done
You’re done when the old system receives zero production traffic, owns no data that anything else reads, runs no scheduled jobs, and could be switched off without anyone noticing – and then you switch it off. Until it’s off, you’re still paying for it in hosting, security patching and the mental overhead of two systems.
Track three numbers on a dashboard the whole team sees: percentage of requests served by legacy, number of tables still owned by legacy, and number of legacy background jobs. All three should trend to zero.
When the strangler fig pattern isn’t the right fit
It needs an interception point. Systems without one – a desktop app, a batch system driven by file drops, deeply embedded firmware – need a different approach. It’s also overkill for a small codebase that a couple of engineers could refactor in place in a few weeks.
And it doesn’t fix a bad domain model by itself. If you strangle a monolith into services that mirror its existing tangled boundaries, you get a distributed tangle. The modular monolith guide covers getting the boundaries right first, which is often the better first step anyway.
If you’re planning a migration like this and want hands-on help working through it with your team, that’s the core of my legacy modernization work. The broader options – rehost, replatform, rearchitect, replace – are covered in legacy modernization strategies.
Questions
What is the strangler fig pattern?
A legacy migration approach where a routing layer sits in front of the old system and requests are moved, slice by slice, to new code until the old system handles nothing and can be removed. Martin Fowler named it after strangler figs, which gradually envelop and replace their host tree.
Is the strangler pattern the same as the strangler fig pattern?
Yes. Fowler originally called it the Strangler Application in 2004 and later renamed it Strangler Fig Application to make the botanical metaphor clearer. “Strangler pattern”, “strangler fig pattern” and “strangler application” all describe the same incremental replacement approach.
How long does a strangler fig migration take?
It depends on the size of the system, but the first slice should be in production within weeks, not months. Full migrations of a mature SaaS monolith commonly take a year or more. The advantage is that value arrives continuously and you can pause without being stuck halfway.
How do you handle the database in a strangler fig migration?
Start with a shared database for low-risk read slices if you must, but aim for each migrated slice to own its data. During the transition, keep one source of truth and sync the other side through change data capture, an outbox with events, or flagged dual writes. Avoid bidirectional sync.
Planning a migration and dreading the rewrite?
In a two-week sprint I’ll set up the routing layer with your team and move the first slice to production. You’ll know whether the approach works before committing further.
GMT+2 · Full EU overlap · US East mornings