Strangler fig pattern
Learn how the Strangler Fig pattern replaces legacy monoliths incrementally, which seams to cut first, and why database migration is harder than the API layer will ever be.
TL;DR
- The Strangler Fig pattern replaces a legacy monolith by extracting functionality piece by piece into new services, routing traffic incrementally through a facade, until the old system handles nothing and can be decommissioned.
- The pattern gets its name from the strangler fig tree (genus Ficus): a vine that grows around a host tree, takes over its structure, and eventually the host rots away leaving only the vine standing as the new tree.
- Almost every team gets Phase 1 (add the facade) and Phase 2 (extract a few services) right. The 80% that fail do so in Phase 3 (database migration) and Phase 4 (actually decommissioning the monolith, not just ignoring it).
- The strangler facade is not a long-term architecture. It is a temporary routing layer that should get thinner over time, not fatter. If your facade is growing in feature logic, your migration is going backwards.
- Use this when the monolith is in active development, too risky to rewrite, and needs to coexist with new services during a multi-month migration. If you can fully rewrite in under three months, that is almost always cleaner.
The Problem
Your e-commerce platform was built in 2014. It is a Django monolith: 240,000 lines, one database with 340 tables, 12 engineers who deploy together every Tuesday at midnight, and one engineer who is the only person who understands the payment processing code. It has worked fine until now.
Now the CEO wants real-time inventory, a checkout supporting multiple payment providers, and per-region pricing. Every one of those features touches at least three modules in the monolith. The last feature like this took four months and introduced two production incidents.
The team proposes a rewrite. The tech lead estimates six months. The business rejects it: six months of zero delivery with unknown risk is not a trade they will make.
The strangler fig pattern is the engineering answer to this constraint: modernize incrementally, never stop shipping, keep the old system running as a fallback at every step.
One-Line Definition
The Strangler Fig pattern intercepts all incoming traffic via a facade, routes requests to either the legacy monolith or newly extracted services based on URL path or feature flags, and incrementally shifts routing until the monolith receives zero traffic and can be decommissioned.
Analogy
Strangler fig trees germinate in the canopy of a host tree, far above the ground. The seed sprouts roots that grow downward around the host trunk, wrapping tighter each year. The strangler gradually takes over the structural role of the host tree. Eventually the host rots away inside the strangler's root cage, leaving a hollow column with the new tree standing in its place.
Software migration works identically. The facade is the strangler seed: it wraps all traffic interception around the existing system. Services are the roots: they grow alongside the monolith, taking over specific routes one at a time. The monolith is the host tree: still running for months or years, processing less and less until it can be shut down quietly.
Users never experience a cutover moment. The host tree just stops being needed.
Solution Walkthrough
Phase 1: Introduce the strangler facade
You add a thin HTTP proxy (an Nginx config, an API gateway routing rule, or a small Node.js proxy) in front of the monolith. At this point, the facade routes 100% of traffic to the monolith. Nothing changes for users. This step is about getting the infrastructure in place before you need it.
The facade becomes the single entry point for all traffic going forward. I always tell teams to do this step in week one before writing a single line of new service code. If installing the proxy causes problems, you learn that early when nothing else is at risk.
Phase 2: Extract the first service (choose the right seam)
You identify the first functionality to extract and build a new service that implements it. The facade now routes specific paths (e.g., /auth/*) to the new service while everything else still goes to the monolith.
The choice of what to extract first matters. Pick something that is: genuinely isolated with minimal data dependencies, high-value to the business (demonstrates the new architecture working), and low-risk (not in the critical transaction path). Search services and reporting modules are often good first candidates.
Phase 3: The incremental extraction loop
Each sprint, one module gets extracted. The facade routing table grows. The monolith handles fewer routes.
New services handle more. This loop continues until the monolith hosts only the hard-to-extract core.
The facade is completely transparent to callers. From any client's perspective, they are talking to one coherent system throughout the entire migration.
Phase 4: Decommission the monolith
Once all routes are migrated, verify with observability that the monolith receives zero traffic for at least one full sprint. Keep it deployed as a safety net for two more sprints. Then shut it down.
Most teams never fully reach Phase 4 because of the database problem. That makes the database migration plan the first thing to establish, not the last.
Implementation Sketch
Here is a minimal strangler facade in TypeScript. This is a sketch; production implementations use Nginx, Kong, AWS API Gateway, or Envoy with proper observability.
// strangler-facade.ts β SKETCH
// Production note: use a proper API gateway (Nginx, Kong, Envoy) rather than
// self-managed Node.js for anything beyond local development.
// This sketch shows routing logic only β not the production mechanism.
interface RouteConfig {
pattern: RegExp;
target: "monolith" | "new-service";
serviceUrl?: string; // Required when target is "new-service"
featureFlag?: string; // Optional: gradual rollout via flag
}
const routeTable: RouteConfig[] = [
// Fully extracted β all traffic goes to new service
{ pattern: /^\/auth\//, target: "new-service", serviceUrl: "http://auth-service:3001" },
{ pattern: /^\/users\//, target: "new-service", serviceUrl: "http://user-service:3002" },
// Partial rollout via feature flag β only flagged users hit the new service
{
pattern: /^\/orders\//,
target: "new-service",
serviceUrl: "http://order-service:3003",
featureFlag: "orders-new-service",
},
// Not yet extracted β monolith handles these
{ pattern: /^\/checkout\//, target: "monolith" },
{ pattern: /^\/.*/, target: "monolith" }, // catch-all: monolith is the safe default
];
async function routeRequest(req: Request, userId: string): Promise<Response> {
for (const route of routeTable) {
if (!route.pattern.test(req.path)) continue;
if (route.featureFlag && !isFeatureEnabled(route.featureFlag, userId)) {
// Flag is off: send to monolith even if route has a new service configured
return forwardTo(req, "http://monolith:8080");
}
const target =
route.target === "new-service" ? route.serviceUrl! : "http://monolith:8080";
return forwardTo(req, target);
}
return forwardTo(req, "http://monolith:8080"); // safe fallback
}
The route table is the entire migration state at any point in time. Every extracted service adds an entry. When the only remaining entry is the catch-all pointing to the monolith, you are done.
Feature flags are your emergency brake
Every route pointing to a new service should have a feature flag. If the new service has a production bug, flipping the flag routes traffic back to the monolith within seconds. No deployment, no incident war room. Without this safety valve, a bug in an extracted service requires a deployment to roll back, which means engineer intervention at 2 a.m.
Going Deeper: What the Tutorials Skip
This section contains the knowledge that separates someone who has read about Strangler Fig from someone who has shipped it. None of this is in the Wikipedia entry.
Database strangling: the real migration
The strangler fig pattern is almost always discussed as an API migration. Teams celebrate extracting the Auth Service. They show a demo of requests routing correctly. Then they discover: every new service they extracted is still talking to the same PostgreSQL database as the monolith.
True service independence requires each service to own its data. The path there is the expand/contract pattern:
- Expand: add the new column or table alongside the old one.
- Dual write: deploy application code that writes to both old and new schemas simultaneously for every new transaction.
- Backfill: migrate existing historical data from the old schema to the new.
- Switch reads: redirect all queries to the new schema. Dual writes continue until fully verified.
- Contract: after two sprints of monitoring, drop the old column or table.
This is measured in months per domain, not days. For a monolith with 340 tables, database strangling is a multi-year project even if the API extraction takes six months.
The shared database trap is the most common anti-pattern
Teams commonly claim 'We migrated to microservices' while 12 services all read the same PostgreSQL schema through shared ORM models. This is distributed monolith β all the operational complexity of microservices with none of the independent deployability. The real test: can you upgrade, scale, and deploy each service completely independently without coordinating with any other team? If not, the database is still the monolith.
Dark launching: validate before you commit
Before routing real user traffic to a new service, send shadow requests. The facade sends each request to both the monolith (primary) and the new service (shadow), uses the monolith's response for the user, and logs any differences for analysis.
Continue Reading with Premium
Unlock this article and every other in-depth system design guide on the platform with NotesFromSDE Premium.