Strangler Fig Pattern
debt(d9/e7/b7/t5)
Closest to 'silent in production until users hit it' (d9). The detection_hints explicitly state automated: no, and there is no code pattern that any tool can flag. Misapplication of this pattern — such as not having a proxy layer, migrating too many components at once, or failing to delete old code — produces no compiler warning, no linter output, and no runtime error. The codebase simply accumulates structural debt silently, often only becoming visible when the migration stalls or the legacy system persists indefinitely.
Closest to 'cross-cutting refactor across the codebase' (e7). The quick_fix describes introducing a proxy/facade layer, routing traffic via feature flags, and incrementally decommissioning old code across the entire system. This is inherently a cross-cutting concern spanning routing infrastructure, all migrated components, and the legacy codebase. Correcting a misapplication (e.g., no proxy layer, simultaneous migrations, or undeletted legacy code) requires changes across multiple layers and components — well beyond a single-file or single-component fix.
Closest to 'strong gravitational pull' (b7). The applies_to covers web and API contexts broadly and the tags indicate this is an architectural-level migration strategy. Once a team commits to this pattern, the proxy/facade layer and the dual-system routing logic shape every subsequent feature and migration decision. Every new route or feature must be evaluated against the migration boundary. The ongoing presence of both old and new systems creates persistent coordination overhead for all developers until the migration completes.
Closest to 'notable trap — a documented gotcha most devs eventually learn' (t5). The misconception field explicitly identifies the trap: developers believe the pattern requires a big-bang cutover at the end, which directly contradicts the pattern's core purpose of avoiding big-bang cutovers. Common mistakes reinforce this — teams also frequently skip the proxy/facade layer, migrate too much at once, or forget to delete old code. These are well-documented gotchas but not so counterintuitive as to be catastrophic for a competent developer who reads about the pattern carefully.
Also Known As
TL;DR
Explanation
The Strangler Fig pattern (Martin Fowler, named after the plant that slowly replaces its host tree) migrates a legacy system by building new functionality alongside it and gradually routing traffic to the new system. A routing layer (facade) intercepts requests and directs them to old or new code based on what's been migrated. Over time, the legacy system shrinks as the new one grows, until the old system can be safely decommissioned. This avoids the risk and disruption of a big-bang rewrite.
Diagram
flowchart TD
subgraph Phase 1 - Intercept
C1[Client] --> PROXY[Proxy / Facade]
PROXY -->|all requests| LEGACY[Legacy Monolith]
end
subgraph Phase 2 - Migrate
C2[Client] --> PROXY2[Proxy]
PROXY2 -->|/users| NEW[New User Service]
PROXY2 -->|everything else| LEGACY2[Legacy Monolith]
end
subgraph Phase 3 - Complete
C3[Client] --> PROXY3[Proxy]
PROXY3 --> NEW2[New Services]
LEGACY3[Legacy<br/>decommissioned]
end
style NEW fill:#238636,color:#fff
style LEGACY fill:#f85149,color:#fff
style NEW2 fill:#238636,color:#fff
style LEGACY3 fill:#d29922,color:#fff
Common Misconception
Why It Matters
Common Mistakes
- Not defining a proxy/facade layer to route between old and new — migrations happen directly in the old codebase.
- Migrating too many components simultaneously — the pattern works because it is incremental.
- Not deleting old code after migration — the legacy system remains and accumulates debt alongside the new.
- Applying the pattern to entirely greenfield development — it is a migration strategy, not an architecture.
Avoid When
- The legacy system is too tightly coupled to allow incremental routing — full rewrite may be unavoidable.
- The team lacks the discipline to maintain both systems — the old system rarely gets retired in practice.
- Short-lived projects where the migration will outlast the business need.
- Systems where traffic cannot be routed at the boundary — embedded or monolithic deployments with no HTTP layer.
When To Use
- Large legacy rewrites where replacing everything at once carries too much risk.
- Systems that must remain live during migration — zero-downtime replacement.
- When individual bounded contexts or features can be extracted and rerouted incrementally.
- Teams that want to validate the new system on real traffic before full cutover.
Code Examples
// Big-bang rewrite instead of incremental strangling:
// Day 1: Stop all development on legacy app
// Week 12: Rewrite complete (it never is)
// Week 16: Still debugging, users still on legacy
// Strangler fig: route /new-feature to new app, /legacy-feature to old — migrate gradually
// Gradually replace legacy by routing traffic to new implementation
class OrderController {
public function store(Request $req): Response {
if ($this->flags->isEnabled('new_order_service')) {
return $this->newOrderService->place($req->validated());
}
return $this->legacyOrderProcessor->process($req->all());
}
}
// Increase rollout: 5% → 25% → 50% → 100%
// At 100% and stable, delete the legacy path