The strangler fig pattern replaces a legacy system gradually instead of in one rewrite. A routing layer goes in front of the old system, new components take over one capability at a time, and old and new run side by side until each old part can be switched off. The pattern is easy to describe. Projects succeed or stall on three decisions: what to route, who owns each piece of data during the transition, and how to cut over in steps you can reverse.
AWS Prescriptive Guidance describes the pattern as incremental replacement: a facade intercepts requests to the legacy system and routes each one either to the legacy application or to a new service, so the two coexist while functionality moves across. The name, coined by Martin Fowler, comes from strangler fig plants, which grow around a host tree until they can stand on their own.
The appeal is risk control. Smaller moves make testing, observation and recovery easier, while unaffected capabilities keep working on the old system. Reversibility still needs to be designed, especially after data or external actions change. The price is a period of running two systems, plus the routing and synchronization work that coexistence needs.
The pattern fits best when the system can be reached through interfaces you can intercept, such as HTTP endpoints, APIs, queues or file drops, and when its capabilities can be separated. It fits poorly when everything happens inside one database through shared stored procedures with no seams, or when the system is small enough that a careful replacement is simpler. If your goal is to add AI features to the old system rather than replace it, our guide to integrating LLMs into legacy software covers that narrower job.
A slice is one business capability with its own inputs, outputs and data, such as loyalty points, delivery quotes or customer notifications. A good first slice:
- has a boundary you can describe in one sentence;
- has callers you can list completely, from logs or traffic capture rather than memory;
- matters enough to prove value, but is not the capability where an outage would hurt most;
- owns data that few other modules write to.
Map the callers and the data before writing new code. Hidden consumers, such as a nightly report that reads a table directly, are a common reason a migrated capability breaks something nobody expected.
Introduce the routing layer before moving anything. At first it forwards every request to the legacy system unchanged. Once it runs in production with logs and metrics, you have one place to make every later change.
Routing rules can match a path, an operation, a customer group or a feature flag, and can shift a share of traffic gradually. Keep the rules in version control, review them like code, and make sure each response can be traced to the component that produced it. The routing layer might be an API gateway, a reverse proxy or a module inside the application. Which one matters less than its being reliable, observable and quick to change.
Figure 1 — Route capability by capability. Preserve a clear ownership boundary.
Data is where coexistence gets difficult. Our rule is that at any moment, each data set has exactly one authoritative writer.
- Before cutover, the legacy system owns the data. The new component reads a synchronized copy, for example from change events or scheduled exports, and does not accept authoritative production writes.
- At cutover, ownership moves. Stop the old writer, drain in-flight work, catch up and reconcile the new copy, then enable the new authoritative writer. Changes flow back to the legacy database only for consumers that still need them.
- After retirement, the sync is removed together with the legacy code that depended on it.
Avoid open-ended dual writes, where application code writes every change to both systems. When one write succeeds and the other fails, the systems drift apart silently. If both must be updated, keep one owner and one sync path, reconcile the two regularly and set an end date. Capture changes durably, retry safely, and make replay idempotent; account for event ordering, deletions and schema versions. Keep a mapping between legacy and new identifiers from day one; you will need it for reconciliation, support queries and rollback.
Figure 2 — One authoritative writer. Transfer ownership after reconciliation.
Plan each slice through a sequence with explicit recovery conditions:
- Compare. Shadow reads or run writes in an isolated simulation with external side effects disabled. Compare outputs without sending duplicate payments, emails or stock updates.
- Move reads. Serve reads from the new component while the legacy system remains the owner.
- Move writes. Transfer ownership with one authoritative writer. A gradual rollout is possible only across safely separable partitions with stable tenant or entity keys. Never randomly send writes for the same data to both systems; if ownership cannot be partitioned, hand over the capability as a unit.
- Retire. Remove the legacy path and sync once exit criteria and recovery checks pass. Keep required recovery records before destructive schema cleanup; retirement itself may be hard to reverse.
Write the rollback plan before each step: the signals that trigger it, who decides, and how the route switches back. After writes have moved, reconcile changes back into a compatible legacy state and fence the new writer before returning authority. Switching a route alone can expose stale data, and it does not undo external effects. Rehearse the data handoff and any corrective actions as well as the route switch.
This example is illustrative, not a client case. A retailer's ERP handles orders, stock and loyalty points. Loyalty has clear inputs, completed orders and returns, and clear outputs, the balances shown at checkout and in the app, so the team chooses it as the first slice.
The routing layer goes in front of the ERP's loyalty endpoints and forwards everything unchanged. The new loyalty service is built and fed order events, and for a period it computes balances in parallel so they can be compared with the ERP's. Once the comparisons agree, balance reads move to the new service. Next, the service becomes the owner of loyalty transactions, and a one-way sync keeps the ERP's loyalty table current for a finance report that still reads it. When that report is rebuilt on the new service, the sync and the old loyalty module are removed.
Another risk is a migration that never finishes, leaving two systems running indefinitely. Keep a one-page map of which capabilities are legacy, in transition or migrated; set exit criteria for each slice; and track retirements as deliberately as launches.
Review the target design periodically against the six pillars of the AWS Well-Architected Framework: operational excellence, security, reliability, performance efficiency, cost optimization and sustainability. The framework is a review structure, and using it does not require AWS services. The same habits of explicit boundaries, data ownership and observability apply to new products, as our guide to architecture decisions from MVP to scale explains. If the trigger for modernization is an unsupported framework version, compare an in-place upgrade with moving slices; our article on Laravel and Node.js for enterprise APIs discusses that choice.
If the system you are modernizing runs a website or customer portal, our website design and development service can help plan the first slice and its routing.
- Amazon Web Services. Strangler fig pattern, AWS Prescriptive Guidance: incremental replacement behind a facade, with legacy and new components coexisting during migration.
- Amazon Web Services. AWS Well-Architected Framework: the six pillars used for architecture reviews.
The data-ownership, cutover and rollback recommendations in this article are TecnoStack's engineering guidance, not requirements from AWS.




