Legacy system migrations are where careers go to die. The “big rewrite” approach—rebuild everything from scratch, then switch over—fails so often that it’s become a cautionary tale in software engineering. Yet legacy systems need to evolve. They become expensive to maintain, difficult to modify, and can’t keep pace with business needs.
The strangler fig pattern offers an alternative: replace the legacy system piece by piece while it continues running. No big bang. No eighteen-month rewrite that’s always six months from completion. Incremental replacement with incremental value.
The Pattern
The name comes from strangler figs—trees that grow around a host tree, eventually replacing it entirely while the host dies. In software terms:
- Intercept requests to the legacy system through a routing layer
- Build new functionality in a new system
- Route requests for that functionality to the new system
- Repeat until nothing routes to the legacy system
- Decommission the legacy system
The legacy system never goes away in a big bang. It gradually handles less and less until it handles nothing at all.
The Routing Layer
The heart of the strangler pattern is the routing layer that sits in front of both systems. This could be:
An API gateway or reverse proxy. Route by URL path, header, or other criteria. /api/v2/orders goes to the new system; /api/orders continues to the legacy system.
A feature flag system. Route based on user, tenant, percentage, or feature flags. “Enterprise customers use the new order system; everyone else stays on legacy.”
A service mesh. Route based on traffic rules, with gradual percentage shifts from legacy to new.
Application-level routing. The legacy application itself forwards certain requests to the new system. Useful when you can modify the legacy code.
The routing layer enables the gradual shift without end users knowing which system serves their request.
Identifying Migration Boundaries
Not all parts of a legacy system are equally good candidates for strangler migration:
Good boundaries:
- Distinct functional areas with clear inputs and outputs
- Features that can be isolated without breaking others
- High-value areas where new functionality is needed anyway
- Sections with relatively clear data ownership
Problematic boundaries:
- Tightly coupled functionality where everything depends on everything
- Shared data that multiple parts of the system read and write
- Features with undocumented dependencies
- Core infrastructure that everything relies on
Start with parts of the system that have natural seams. If the legacy system handles orders, inventory, and reporting, and these are somewhat independent, each is a potential migration unit.
The Data Problem
Strangler migrations get complicated when both systems need the same data. Options:
Shared database (carefully). Both systems read from and write to the same database. This works when:
- The schema can support both systems
- Transaction boundaries are clear
- You’re migrating functionality, not data structure
The risk: the shared database becomes a coupling point that constrains how the new system can evolve.
Data synchronization. Each system has its own data store, with synchronization between them. Changes in legacy sync to new; changes in new sync to legacy.
This works when:
- You can accept eventual consistency
- Conflict resolution rules are clear
- Sync can be implemented reliably
The risk: synchronization bugs cause data inconsistencies that are painful to diagnose and repair.
Dual writes (with caution). The routing layer or application writes to both systems. Reads go to one (usually legacy until the new system is trusted).
This works for:
- Transitional periods
- Low-write scenarios
- When you need to validate the new system before trusting it
The risk: dual write failure modes are nasty. What happens when one write succeeds and the other fails?
Event-driven approach. Changes publish events; both systems subscribe and update their own state. Over time, the new system becomes the source of truth.
This works when:
- You’re building event infrastructure anyway
- The domain can be modeled as events
- You have expertise in event-driven patterns
The risk: complexity of event infrastructure and eventual consistency reasoning.
There’s no perfect solution. Pick based on your constraints and accept that data migration is the hard part of any strangler project.
Practical Migration Flow
A realistic strangler migration looks like this:
Phase 1: Establish the routing layer.
Before migrating anything, put the routing infrastructure in place. Route 100% of traffic to legacy. Verify that the routing layer itself doesn’t break anything.
Phase 2: Build the first new component.
Pick a relatively isolated piece of functionality. Build it in the new system. Get it working with test traffic or a small percentage of real traffic.
Phase 3: Gradual cutover.
Route increasing percentages of traffic to the new component. 1%, 10%, 50%, 100%. Monitor for errors, latency, and correctness at each step. Roll back if problems appear.
Phase 4: Iterate.
Repeat phases 2-3 for additional components. Each iteration builds confidence and infrastructure that makes subsequent migrations easier.
Phase 5: Decommission.
When legacy handles no traffic, turn it off. Ideally keep it around in read-only mode briefly, in case you need historical data or want to verify the new system.
What Actually Goes Wrong
Underestimating coupling. “These components are independent” turns out to mean “these components share seventeen undocumented dependencies.” Discovery of coupling mid-migration is demoralizing.
Data migration gets stuck. The dual-system data situation was supposed to be temporary but becomes permanent. Both systems accumulate divergent data. Nobody wants to touch it.
New system becomes legacy too. The migration takes so long that the “new” system’s architecture becomes outdated before it’s complete. You’re migrating from one legacy system to another.
Business changes during migration. The target you’re migrating toward shifts as business requirements evolve. Scope creep is real.
Team fatigue. Long migrations are exhausting. Teams lose context as people rotate. Motivation drops as the finish line remains perpetually distant.
Mitigation Strategies
Time-box migration phases. Each phase should deliver value within 3-6 months. If a phase is taking longer, it’s probably scoped too large.
Measure progress continuously. What percentage of traffic goes to the new system? How much functionality remains in legacy? Track these metrics and make them visible.
Build data migration tooling early. Don’t treat data synchronization as an afterthought. Invest in tooling to migrate, sync, and verify data from the start.
Accept good enough. Not every feature in the legacy system needs to be recreated. Some can be deprecated during migration. Some can be simplified. Migrating with 100% feature parity is often unnecessary and slow.
Plan for maintenance of both systems. During migration, you’re operating two systems. Both need monitoring, bug fixes, and security patches. Budget for this overhead.
When Strangler Isn’t Right
The strangler pattern isn’t always the best approach:
Tightly integrated monoliths. If the legacy system is so coupled that nothing can be extracted independently, strangler creates more complexity than it solves. You might need to accept a rewrite or significant refactoring before strangler becomes viable.
Simple replacements. If the legacy system is small and well-understood, a direct replacement might be faster. Strangler overhead isn’t justified for a two-month rewrite.
Data model fundamental changes. If the new system requires a completely different data model, synchronizing between old and new during migration may be impractical. A migration-then-switch approach might be cleaner.
No routing layer option. If you can’t insert a routing layer (embedded systems, certain legacy architectures), the pattern doesn’t apply.
An Example: E-Commerce Order System
Imagine a legacy e-commerce system where you want to replace the order management component:
Phase 1: Deploy an API gateway in front of the legacy system. All traffic routes to legacy. Verify everything works.
Phase 2: Build a new order service with a clean API. Implement order creation in the new system.
Phase 3: Route new order creation requests to the new system. Orders write to both the new system’s database and the legacy database (for backward compatibility).
Phase 4: Migrate order retrieval to the new system. Both systems can serve order data. Route retrieval requests to the new system while continuing to sync data.
Phase 5: Migrate order modification, cancellation, and related functionality incrementally.
Phase 6: Once all order functionality is in the new system and data sync is stable, remove the legacy order tables from active use. Eventually decommission the legacy order code entirely.
This took a major e-commerce company two years. Along the way, they shipped new order features they couldn’t have built on the legacy system. The migration delivered value throughout, not just at the end.
The Bottom Line
The strangler fig pattern makes legacy migration survivable. Instead of betting everything on a big-bang switchover, you migrate incrementally with lower risk and continuous value delivery.
It’s not magic. Data migration is still hard. Coupling creates complications. Long migrations still exhaust teams. But compared to the alternative—“stop the business for 18 months while we rewrite”—strangler is usually the safer path.
The key is treating migration as a program of incremental improvements, not a project with a single delivery date. That mindset shift, more than any technical pattern, is what makes strangler fig migrations succeed.