The strangler fig pattern for legacy migrations
Big-bang rewrites bet everything on a date. The strangler fig pattern wins by routing traffic one endpoint at a time — building the new system around the old until the legacy core is hollow and safe to remove.
The rewrite was eighteen months in. Launch date arrived. Traffic switched. Three weeks later, traffic switched back. The new system handled happy paths; edge cases lived only in the legacy codebase nobody had fully mapped. Payroll integrations, admin overrides, ten-year-old promo codes — the big-bang bet that a date on a calendar could replace incremental truth. Strangler fig legacy migration takes the opposite bet: route one capability at a time, prove each slice in production, keep rollback trivial until the old system has nothing left to do.
The pattern names a fig that grows around a host tree, eventually replacing it. In software: a facade or router sits in front of the legacy system. New implementations absorb endpoints, events, or data paths. The legacy core shrinks until decommissioning is a small project, not a company-wide risk event.
Big-bang rewrites fail on unknown unknowns
| Big-bang risk | Why it compounds |
|---|---|
| Hidden business rules | Only surface under production edge cases |
| Parallel development | Legacy still changes during rewrite |
| Integration inventory gaps | Undocumented consumers appear at cutover |
| All-or-nothing rollback | Revert is as risky as launch |
| Morale cliff | Long dark periods before validation |
Rewrites feel clean in architecture diagrams. They are messy in ticket backlogs, partner SLAs, and compliance audits. The strangler fig accepts messiness upfront — dual systems, routing complexity, temporary data duplication — in exchange for continuous delivery of value and reversible steps.
A migration without per-step rollback is a rewrite wearing a project plan.
Anatomy of a strangler fig migration
Facade / router. Single entry point for clients — API gateway, reverse proxy, or application router. All traffic passes through. Routing rules decide legacy vs new implementation per endpoint, feature flag, or tenant.
Slice selection. Choose boundaries with clear inputs and outputs — one REST resource, one event type, one batch job. Not "the billing module" — POST /invoices, InvoiceCreated event, nightly reconciliation job.
New implementation. Build behind the facade. Shadow mode optional: new system processes requests but does not respond to clients — compare outputs before cutover.
Traffic shift. Percentage-based, tenant-based, or full cutover per slice. Feature flags control routing without redeploying the facade.
Legacy shrink. Remove routed code from legacy only after sustained proof — metrics green, no rollback for N days, reconciliation clean.
Decommission. When legacy handles zero production traffic and no batch dependencies, retire infrastructure.
Clients → Facade/Router
├─ /users/* → New User Service
├─ /orders/* → New Order Service
├─ /reports/* → Legacy Monolith
└─ /admin/* → Legacy Monolith (last)
The modular monolith startup architecture article describes internal module boundaries that become natural strangler slices — extract a module to a service without rewriting the whole application.
Data migration is the hard part routing does not solve
Routing HTTP traffic is the visible layer. Data often outlives code.
| Data pattern | Strangler approach |
|---|---|
| Read-heavy slice | New service reads replica; legacy still writes until cutover |
| Write migration | Dual-write period — both systems accept writes, reconciliation job compares |
| Event-driven sync | Legacy emits change events; new system builds read model |
| Shared database anti-pattern | Extract schema slice to new DB with sync; last resort is shared tables with strict ownership |
Dual-write periods need reconciliation — compare row counts, checksums, sample diffs on schedule. Divergence discovered at decommission time is expensive. Divergence discovered nightly is manageable.
Never delete legacy data paths until the new path has processed production traffic through a full business cycle — month-end close, billing cycle, compliance reporting window.
Slice ordering strategy
Not all endpoints are equal migration candidates.
Migrate first:
- High-churn, well-understood domains with good test coverage
- Read-heavy paths where stale legacy writes are tolerable briefly
- Features blocking new product direction
- Endpoints with few undocumented consumers
Migrate last:
- Admin and override paths with implicit business rules
- Batch jobs with calendar dependencies
- Integrations with external partners requiring recertification
- Areas where "how it works" lives in one engineer's head
Each slice ships independently. Celebrate slice completion — not only final decommission. Morale survives incremental proof.
Facade operations: routing, observability, rollback
The facade is production infrastructure. Treat it accordingly.
- Routing config as code — versioned rules, reviewed changes, audit trail.
- Per-route metrics — latency, error rate, traffic split legacy vs new.
- Instant rollback — flip flag or routing rule to 100% legacy for one slice without affecting others.
- Request tracing — correlation ID through facade to implementation; essential for shadow mode comparison.
Feature flags as release infrastructure overlap with strangler routing — flags control which implementation serves traffic. The difference is strategic intent: flags gate features; strangler flags gate architectural replacement.
How should teams plan a strangler fig migration?
These questions prevent facades that become permanent complexity.
When is strangler fig better than a rewrite?
When legacy is in production with real users, hidden rules, and integrations that cannot pause. When the team needs to ship new capabilities during migration. When rollback per slice is a requirement. Rewrite may suit greenfield replacement with no production dependency — rare in mature systems.
How small should a slice be?
Small enough to rollback in one routing change. Large enough to deliver meaningful value. Typical: one API resource group, one event consumer, one scheduled job — weeks to months, not days or years.
What stops the facade from becoming permanent?
Track legacy traffic percentage per route. Set decommission criteria upfront: "legacy /orders traffic = 0 for 30 days." Facades without sunset metrics become dual-system operations forever.
A common argument runs the other way
The opposing view holds that maintaining two systems costs more than a fast rewrite — that strangler fig prolongs pain and duplicates effort.
Dual-system cost is real and should be budgeted. Big-bang failure cost — revert, lost months, customer impact — is larger and less predictable. Strangler fig caps downside per slice. The total calendar time may exceed a successful big-bang, but big-bangs that fail do not have a total calendar time — they have a restart.
Teams with limited capacity benefit precisely because slices fit into normal sprints alongside feature work. Rewrites consume the roadmap.
Key takeaways
- Strangler fig migrates one endpoint or capability at a time behind a facade router.
- Big-bang rewrites fail on edge cases and integrations discovered only at cutover.
- Data dual-write and reconciliation matter as much as traffic routing.
- Migrate well-understood read-heavy slices first; admin and batch jobs last.
- Per-slice rollback via routing flags keeps migration reversible.
- Track legacy traffic per route — facades without sunset metrics become permanent.
Conclusion
Legacy migration is not a project with a finish line on a Gantt chart. It is a sequence of small proofs that the new system can carry production truth — one slice at a time, with the old system still standing until each proof holds.
The first step is not writing new code. It is inventory: list every entry point to the legacy system, measure traffic and risk per route, pick the smallest high-value slice, and put a router in front. The fig grows from there.