The strangler fig pattern takes its name from a vine that grows around a host tree, slowly replacing it until the original is gone. In software, it describes a migration strategy where a new system is built alongside the old one, with traffic incrementally rerouted until the legacy code is hollow and can be removed. It's a far cry from a big-bang rewrite, and for most Australian dev teams running long-lived monoliths, it's the only approach that doesn't require stopping the business to do it.
Martin Fowler named and popularised the pattern in 2004, but it has never been more relevant than now, when the average enterprise codebase is older than the cloud platforms meant to replace it. The pattern works at any scale, from a ten-year-old Django monolith serving 50,000 users to a mainframe-backed system handling financial settlements.
What the pattern actually involves
The core mechanic is a façade, sometimes called a proxy or an anti-corruption layer, that sits in front of both systems. Requests arrive at the façade. The façade routes them to either the old system or the new one, depending on which parts have been migrated. Over time, more routes point to the new system. Eventually, nothing points to the old one.
Three steps repeat throughout the migration. First, identify a capability in the legacy system that can be extracted cleanly. Second, build that capability in the new system. Third, shift the relevant traffic. The old code keeps running for everything else, untouched. There's no cutover date everyone dreads. There's no weekend freeze where the entire team holds its breath.
The façade itself is usually a reverse proxy, an API gateway, or a thin application layer. Teams using AWS commonly use an Application Load Balancer with path-based routing. Teams on Azure use API Management. Some teams build a lightweight service in Go or FastAPI that does nothing but route and log. The technology matters less than the principle: the façade must be the only entry point, and it must not contain business logic.
How to slice the legacy system
The hardest decision is not technical. It's organisational: what to migrate first. Three slicing strategies work in practice.
By HTTP route. This is the simplest. If the legacy system is a web application, you map its URL surface and migrate one route group at a time. Authentication flows, user profile management, reporting endpoints, each can be moved independently. The façade routes by URL prefix. It's clean, it's testable, and rollback is a routing change.
By customer cohort. Some teams migrate a percentage of users first, usually starting with internal accounts or beta users. The façade reads a user attribute or a feature flag and routes accordingly. This pattern works well when the new system isn't yet at full feature parity because affected users can be chosen deliberately.
By read vs write. A common early win is migrating read paths before write paths. Reads are typically safer: a failed read returns an error, while a failed write can corrupt data. Teams that migrate reads first gain confidence in the new system under real load before touching anything stateful.
If your team is already using feature flags to control what users see and when, those same mechanisms integrate naturally into strangler fig routing. A flag that controls feature visibility can also control which backend handles a request.
Where teams go wrong
The pattern looks simple and is genuinely elegant in diagrams. In practice, four failure modes recur.
Migrating the database too early. The strangler fig pattern works best when the legacy system and the new system can share a database, at least initially. Teams that split the database at the start are no longer running a strangler fig migration; they're running a distributed data migration simultaneously, which is a different and harder problem. Database migration strategy deserves its own workstream, sequenced after the application layer is stable.
Building the façade wrong. A façade that accumulates business logic becomes a third system to maintain. Keep it dumb. It routes, it logs, it passes headers. That's it. Any logic that belongs in a service should live in a service.
Not defining done. Teams that start strangler fig migrations sometimes discover, two years in, that the old system still handles 30 percent of traffic because the remaining routes were deemed "too hard." The legacy system never dies; it festers. Define a decommission date before you start, review it quarterly, and treat the remaining legacy routes as the highest-priority work, not the lowest.
Skipping the traffic shadowing phase. Before flipping traffic to the new system in production, run both systems in parallel for a period. The new system processes a copy of real requests but its responses are discarded. This surfaces subtle behavioural differences, particularly around edge cases in data formatting and session handling, that synthetic tests never find.
Testing and observability during the migration
The strangler fig migration creates a period where two systems handle production traffic, which means your observability needs to cover both. At minimum, you need correlated traces across the façade and both backends, error rates broken down by route and by system, and latency comparisons between old and new paths for the same operation.
Correctness testing is the hard part. For read paths, comparing responses from old and new systems on the same request is feasible and highly recommended. For write paths, it's harder. Some teams build a reconciliation job that compares the state written by each system on a test cohort. Others rely on integration tests that exercise the new system's writes against a staging environment seeded with production data.
If the team practises trunk-based development, the strangler fig migration fits naturally: each extracted capability ships as a small, independently deployable change rather than a long-lived branch. Short integration loops mean routing changes can be validated and rolled back quickly if something unexpected surfaces.
When the pattern doesn't fit
The strangler fig pattern assumes the legacy system can receive and respond to requests while the new one is being built. That assumption breaks in a few scenarios.
Batch-processing systems that don't expose a request-response interface need a different approach. You can still apply the principle, splitting the batch job's responsibilities and running both systems on separate data partitions, but the façade mechanic doesn't transfer directly.
Tightly coupled database schemas shared across the legacy system and other services also complicate the approach. If twelve different applications write directly to the same tables, extracting any capability cleanly requires first untangling those dependencies, which is a project of its own.
And if the legacy system has no automated tests, migrating any capability introduces correctness risk you can't measure. The minimum viable investment before starting a strangler fig migration is a characterisation test suite against the existing system, capturing its actual behaviour, including the bugs, so the new system can be verified against it.
A realistic timeline
Teams that succeed with the strangler fig pattern almost always underestimate how long it takes to extract the first capability and overestimate how long each subsequent one takes. The first extraction is slow because you're building the façade, establishing the observability baseline, and agreeing on the routing contract, all at once. The second extraction uses everything already in place. By the fifth, the team has a repeatable process.
For a mid-sized monolith (roughly 200,000 lines, 40 route groups), a realistic full migration timeline is 18 to 24 months. Shorter isn't usually honest. Longer usually means the team has lost momentum on decommissioning the remaining legacy routes and needs to recharge the urgency.
Done well, the strangler fig migration ends not with a big go-live but with a quiet deletion: the final pull request that removes the last reference to the old system from the routing table and then, some time later, the infra teardown that finally turns off the servers that ran it.

