Strangler Fig
Strangler Fig incrementally replaces a legacy system by routing traffic slice-by-slice to new implementations while the old system continues running.
Introduction
Strangler Fig incrementally replaces a legacy system by routing traffic slice-by-slice to new implementations while the old system continues running. Named after the fig vine that grows around a host tree until the host dies, the pattern avoids big-bang rewrites — the riskiest migration strategy at Amazon scale where a single hour of checkout downtime costs tens of millions.
Real production story
In 2019, an Amazon retail team owned a 2.1M-line Java monolith powering product detail, cart, and checkout. Leadership demanded microservices for independent deploy, but a 18-month freeze-and-rewrite was rejected after a competitor's failed migration made headlines. The staff architect proposed Strangler Fig: put an API gateway in front of the monolith, extract read-only product catalog first (low risk), then cart mutations, then payment authorization last.
Within six quarters, 73% of read traffic hit new services. The monolith still handled edge cases — returns, gift cards, marketplace seller overrides — but new features shipped only on the strangler path. Prime Day 2020 ran with zero rollback events. MTTR for catalog bugs dropped from 4 hours to 22 minutes because teams owned bounded services with independent deploy pipelines.
Business problem
Business pressure: Amazon's retail platform must ship weekly while maintaining 99.99% availability during Prime Day, Black Friday, and regional flash events. A big-bang rewrite freezes feature delivery for quarters and concentrates risk into a single cutover weekend.
- Revenue at risk: Checkout downtime during peak events directly impacts GMV — leadership will not approve a migration that pauses revenue-generating features for 12+ months.
- Engineering velocity: 400+ engineers touch the monolith; merge queues exceed 3 days; every feature risks regression in unrelated domains.
- Compliance / trust: PCI and marketplace seller SLAs require explainable rollback paths — "we'll cut over Saturday and hope" fails architecture review.
Architecture overview
Strangler Fig in production means a facade (API gateway, reverse proxy, or routing layer) sits in front of legacy and new systems. Each migrated capability gets a routing rule: if the request matches slice N, route to the new service; otherwise, fall through to legacy. Over time, the legacy surface shrinks until decommission.
- Definition: Incremental replacement pattern — new system grows around legacy until legacy can be retired without a coordinated big bang.
- When to adopt: Large legacy with clear domain seams, high availability requirements, and business pressure to ship during migration.
- When to defer: Small codebase (<50K LOC), low traffic, or team can afford a clean rewrite with minimal production risk.
- Operability: Dual-run periods require parity dashboards, shadow traffic comparison, and feature flags per routing slice.
Architecture motivation
Why architects care: Strangler Fig addresses evolvability, risk containment, and continuous delivery simultaneously. The naive alternative — freeze the monolith, rewrite in parallel, switch traffic in one weekend — works in slide decks but fails when production edge cases surface on hour three of cutover.
- Force: Legacy system cannot scale to new business models (marketplace, subscriptions, same-day delivery) without structural change.
- Constraint: Cannot stop feature development; business units compete for roadmap slots every quarter.
- Outcome: Incremental traffic migration with measurable parity, per-slice rollback, and ADRs documenting extraction order.
Internal architecture
Amazon retail strangler topology — gateway routes by capability, not by team:
- Router owns the migration map — not individual services. Central visibility into which paths are strangler vs legacy.
- Extract read before write — catalog reads are idempotent; cart/checkout writes need saga and reconciliation.
- Legacy remains authoritative for unmigrated domains until parity sign-off per slice.
Client (web / mobile / Alexa)↓Application Load Balancer↓Strangler Router (Envoy / custom)↓┌─────────────────────────────────────┐│ Route table (feature-flag driven) ││ /catalog/* → catalog-svc-v2 ││ /cart/items → cart-svc-v2 ││ /checkout/* → legacy-monolith ││ /returns/* → legacy-monolith │└─────────────────────────────────────┘↓ ↓New microservices Legacy monolith(K8s, own DB) (shared Oracle DB)
Data flow
Strangler read path: request hits router → matched to new catalog service → new service reads its own PostgreSQL → response returned. Shadow mode: same request duplicated to legacy asynchronously; parity job compares JSON diff.
- Write path: dual-write to legacy + new store during transition; reconciliation worker fixes drift nightly.
- Read path: route 5% canary traffic to new service; promote to 100% when error rate and p99 match legacy within SLO.
- Async path: domain events from new services backfill legacy read models until legacy decommission.
// Strangler routing middleware — production patterninterface RouteSlice {pathPrefix: string;target: "legacy" | "new";canaryPercent: number;parityCheck: boolean;}const SLICES: RouteSlice[] = [{ pathPrefix: "/v2/catalog", target: "new", canaryPercent: 100, parityCheck: false },{ pathPrefix: "/cart", target: "new", canaryPercent: 25, parityCheck: true },{ pathPrefix: "/checkout", target: "legacy", canaryPercent: 0, parityCheck: false },];function route(req: Request): "legacy" | "new" {const slice = SLICES.find((s) => req.path.startsWith(s.pathPrefix));if (!slice || slice.target === "legacy") return "legacy";const bucket = hashTenant(req.headers["x-customer-id"]) % 100;return bucket < slice.canaryPercent ? "new" : "legacy";}
System design diagram
Two diagrams show the Strangler Fig topology and the primary request/event path used in production at scale.
Production code example
Reference implementation — strangler router with canary, parity shadow, and metrics wired for Amazon-class operations:
- Metrics: RED per slice (rate, errors, duration) split by legacy vs new target.
- Feature flags: canary percent stored in LaunchDarkly / AppConfig — not hardcoded in router.
- Rollback: set canary to 0% in under 60 seconds without redeploy.
// Express + Envoy-style strangler facadeimport express from "express";import { createProxyMiddleware } from "http-proxy-middleware";import { metrics } from "./observability";const app = express();const LEGACY = process.env.LEGACY_URL!;const CATALOG_V2 = process.env.CATALOG_V2_URL!;app.use("/v2/catalog", (req, res, next) => {const useNew = shouldCanary(req, "catalog", 100);metrics.increment("strangler.route", { slice: "catalog", target: useNew ? "new" : "legacy" });if (useNew) {if (process.env.SHADOW_PARITY === "true") shadowCompare(req, LEGACY, CATALOG_V2);return createProxyMiddleware({ target: CATALOG_V2, changeOrigin: true })(req, res, next);}return createProxyMiddleware({ target: LEGACY, changeOrigin: true })(req, res, next);});app.use(createProxyMiddleware({ target: LEGACY, changeOrigin: true }));app.listen(8080);
Enterprise case study
Amazon retail — Strangler Fig over 6 quarters: Platform team built a paved-road strangler template: Envoy route config, parity dashboard, and extraction ADR checklist. Product teams adopted via self-service with architecture review for write-path extractions.
- Before: 2.1M-line monolith; 4-hour MTTR; merge queue 3+ days; one failed big-bang attempt shelved.
- Decision: Strangler with read-first extraction order; central router team owns route table; domain teams own new services.
- After: 73% read traffic on new services; Prime Day zero rollbacks; new feature lead time dropped from 6 weeks to 9 days for migrated domains.
Trade-offs
- Complexity vs risk: Running dual systems doubles operational surface — buys incremental rollback and continuous feature delivery.
- Data parity vs speed: Strict dual-write reconciliation slows extraction — relax for read-only slices, tighten for financial writes.
- Router centralization vs team autonomy: Central route table enables visibility; becomes bottleneck if every team edits it without review.
Security considerations
Security is architectural: Strangler migration doubles the auth surface — legacy session tokens, new OAuth scopes, and cross-system identity mapping must be consistent.
- Identity: Map legacy customer IDs to new service principals; mTLS between router and extracted services — not IP allowlists.
- Data: PCI scope expands during dual-run if both systems touch card data — minimize card data fan-out; tokenize at gateway.
- Supply chain: New services get independent CI/CD with signed images; legacy deploy path remains until slice decommission.
Scalability analysis
Scale dimensions: Amazon engineers plan for Prime Day traffic (10× baseline), geographic expansion, and 50+ extraction slices running concurrently — not just QPS on one service.
- Horizontal scale: Router tier is stateless and autoscales; each extracted service scales independently with its own DB shard strategy.
- Hot spots: Popular ASINs during flash sales dominate catalog reads — new service needs CDN + read replicas before cutover.
- Cost: Dual-run period runs two compute stacks — budget 6–12 months of 1.3–1.8× infra cost during active migration.
Failure scenarios
What breaks: Parity drift between legacy and new catalog causes wrong prices; router misconfiguration sends checkout to unmigrated path; dual-write partial failure leaves inconsistent cart state.
- Parity mismatch: New catalog returns stale inventory — flash sale oversells. Mitigate with shadow traffic diff alerts before canary promotion.
- Router misroute: Feature flag typo sends 100% checkout to new incomplete service — use flag audit log and automatic rollback on error-rate spike.
- Dual-write failure: Legacy write succeeds, new write fails — reconciliation job must detect and repair within SLA (minutes, not days).
Staff engineer insights
- Extract the slice with the clearest domain boundary and lowest write complexity first — not the slice your team finds most annoying.
- Parity dashboards are not optional. If you cannot prove new == legacy within SLO, you are not ready to increase canary percent.
- Decommission legacy paths aggressively once stable — dual-run becomes permanent if teams lose incentive to finish the migration.
Interview questions
Interview Prep
Practice concise answers, then expand each card for the explanation.
1AdvancedQuestionHow do you choose the order of slices in a Strangler Fig migration at Amazon scale?+
Answer
Follow-up
2AdvancedQuestionPrime Day is 8 weeks away. Leadership wants to finish checkout extraction before the event. What do you recommend?+
Answer
Follow-up
3AdvancedQuestionDesign parity checking between legacy and new catalog services without doubling user-facing latency.+
Answer
Follow-up
Architecture review questions
- Is extraction order documented in ADR with risk ranking per slice?
- Does each slice have canary rollout plan, rollback procedure, and parity dashboard?
- Are write-path slices using dual-write + reconciliation, not blind cutover?
- Is legacy decommission date set per slice to prevent permanent dual-run?
- Can router change canary percent in <60s without full redeploy?
- Security: auth mapping between legacy and new verified for each migrated path?
Summary
Strangler Fig at enterprise scale means growing new services around legacy until the host can die — without a big-bang weekend. At Amazon, success depends on slice ordering, async parity, canary routing, and aggressive legacy decommission. Make the migration map visible, measurable, and reversible.