Legacy systems are rarely “bad.” Most are simply overworked: too many features piled onto a core that was never designed for today’s volume, integrations, security expectations, and pace of change. The challenge is that rewriting everything at once is risky, expensive, and usually slower than you hope.
The strangler fig approach is a modernization strategy that replaces a legacy system incrementally. You build new functionality around the old system, redirect traffic piece by piece, and eventually retire the legacy core. Done well, it turns a scary, high-stakes rewrite into a sequence of smaller, reversible steps.
This post gives a practical plan you can apply with a small team: how to choose the first slice, how to route safely, how to deal with data, and where teams commonly go wrong.
What the strangler fig approach is
The name comes from a plant that grows around a host tree. In software terms, the “host” is your legacy application. The “fig” is a growing set of new services, pages, or modules that gradually take over responsibilities until the old system can be removed.
Key characteristics:
- Incremental replacement: You replace one capability at a time (for example, billing screens, customer profile APIs, reporting exports).
- Controlled routing: Requests are directed either to the legacy implementation or to the new one.
- Continuous learning: Each slice teaches you more about hidden requirements and real user behavior.
- Risk management: The migration is designed to be reversible at the slice level.
- Start with a small, high-value slice that has clear boundaries and measurable outcomes.
- Invest early in observability, regression checks, and a fast rollback path.
- Use routing (by endpoint, path, feature flag, or user segment) to move traffic gradually.
- Plan for data realities: dual-writes, event replication, and “source of truth” transitions.
- Make legacy retirement an explicit milestone, not a vague hope.
Pick a good first slice
The first slice sets your trajectory. If you pick something too entangled, you will spend months untangling dependencies and end up concluding that the approach “doesn’t work.” Choose a slice with clean edges and immediate payoff.
What makes a slice “good”
- Clear entry points: A well-defined URL path, API endpoint set, background job, or UI section.
- Limited data surface: It touches a small subset of tables or concepts.
- High pain or high value: It reduces support load, unlocks a feature, or improves performance.
- Testable outcomes: You can measure correctness and success (error rate, completion rate, time to complete).
Concrete example: Imagine a mid-sized distributor with a legacy monolith that handles orders, inventory, and invoicing. Customer service complains that “Order Status” is slow and unreliable, especially for large accounts. This is an ideal first slice: it has a clear user-facing page, a small set of queries, and obvious success metrics (response time and accuracy).
Slice selection checklist
Copy and paste this checklist into your planning doc:
- We can describe the slice in one sentence (who does what, in what interface).
- We know the legacy entry point(s) (paths/endpoints/jobs) that implement it.
- We can list the data inputs and outputs (including side effects).
- We have at least one fast way to validate correctness (golden records, sampled comparisons, or user acceptance steps).
- We have a rollback plan that does not require a database restore.
Create a safety net before you cut over
Incremental migration only feels safe if your team can detect problems early and reverse quickly. Before building the new slice, establish a baseline and define “safe enough” for launch.
- Baseline behavior: Capture what “correct” means using a handful of real scenarios (for example, 20 representative orders across edge cases).
- Observability: Track request counts, error rates, latency, and key business outcomes (like “status page loaded” or “invoice generated”).
- Release controls: Add a feature flag or routing toggle that can move traffic back to legacy in minutes.
- Support readiness: Document what changed, how to recognize failures, and how to roll back.
Even if your organization is not “monitoring mature,” you can still create a practical safety net: simple dashboards, a few alert thresholds, and a daily sampling habit. The goal is not perfection, it is fast feedback.
Build the new slice and route traffic to it
Most strangler fig projects succeed or fail at the routing layer. Routing is where you decide, per request, whether the old or new implementation handles the work.
Common routing strategies include:
- By path:
/orders/statusgoes to the new service, everything else stays on legacy. - By endpoint: New APIs live under a versioned prefix, then you migrate consumers.
- By user segment: Start with internal users, then a small customer cohort, then everyone.
- By feature flag: Flip the new slice on/off without redeploying.
A simple conceptual model is a routing table with clear ownership:
Route decision (conceptual)
- Request matches new-slice paths? yes -> New implementation
- Otherwise -> Legacy implementation
- If error rate exceeds threshold -> Auto-fallback to Legacy
- Log decision for audit and debugging
Practical rollout pattern: start with “shadow mode” (new slice runs but does not serve responses), then “canary” (a small percent of real traffic), then “full cutover.” Shadow mode is especially useful for read-only slices like status pages and reporting.
Real-world style rollout: Order Status modernization
Continuing the distributor example, the team builds a new “Order Status API” backed by a read-optimized store (even a replica or a purpose-built table). For two weeks, the legacy page still serves customers, but every request also calls the new API in the background and compares results for sampled orders.
When the mismatch rate drops near zero and latency is consistently better, the team enables the new page for staff accounts only. After a stable period, they turn it on for 10 percent of customers, then 50 percent, then 100 percent. The legacy implementation remains available as a fallback until the slice is proven.
Handle data migration without freezing the business
Data is the hardest part of modernization because systems are coupled through shared state. The strangler fig approach is still compatible with tricky data, but you need a deliberate plan for “source of truth” over time.
Three practical patterns:
- Read from legacy, write to new: Useful when you can keep legacy as the authoritative store while you validate the new domain model.
- Dual-write with reconciliation: Both systems receive writes, and you detect drift through periodic checks. This is powerful but requires disciplined handling of failures.
- Event replication: Legacy emits events (or change records) that the new system consumes to stay current.
For many teams, the safest sequence is:
- Read-only slice first (status, reporting, search) to learn data shapes and edge cases.
- Write-light slice next where failures are easy to correct (preferences, notes, tags).
- High-integrity writes last (payments, invoicing, fulfillment) after your rollback and reconciliation tools are mature.
Make the “source of truth” explicit in your docs. If the legacy database remains authoritative for orders until a given milestone, state it clearly. Ambiguity here is how teams accidentally create two competing truths.
Common mistakes (and how to avoid them)
- Migrating by internal modules instead of user journeys: Replacing a “service layer” can take forever without delivering visible value. Prefer slices that map to real workflows.
- No measurable definition of correct: If you cannot compare old vs new, you are guessing. Build golden scenarios and sampling comparisons.
- Routing without observability: If you cannot see where requests went, you cannot debug production incidents. Log route decisions.
- Dual-write without a reconciliation story: If one write succeeds and the other fails, what happens? Decide upfront and rehearse it.
- Leaving the old code “for later”: Legacy retirement must be part of the plan, with explicit milestones to delete routes, remove tables, and decommission jobs.
When NOT to do this
The strangler fig approach is a strong default, but not universal. Consider alternatives if:
- The system is small and well-contained: A rewrite might be faster than designing routing and hybrid operation.
- You cannot run two systems safely: Some environments have constraints that make parallel operation extremely costly or risky.
- The domain rules are unknown and undocumented: If the legacy system is the only source of truth and behavior is mostly implicit, invest first in discovery and documentation through tests and baselines.
- You need a platform-level change everywhere at once: For example, a mandatory runtime change that forces redeployment of all components. Even then, you can still use strangler patterns at the functional level, but the infrastructure shift may be the gating item.
FAQ
How long should a single slice take?
A good target is 2 to 8 weeks from “start” to “stable in production,” depending on team size and complexity. If a slice is trending much longer, it is usually a sign the boundaries are not as clean as expected or you chose too large a scope.
Do I need microservices to use the strangler fig approach?
No. A “new slice” can be a new module inside the same repo, a new UI route, or a new API behind the existing app. The defining feature is incremental replacement with controlled routing, not a specific architecture style.
What is the safest first slice if everything feels coupled?
Start with something read-only that is painful but isolated: reporting exports, search, or a single customer-facing status page. Read-only slices let you validate data assumptions without risking write integrity.
How do we prevent the hybrid state from becoming permanent?
Attach a retirement task to every slice: remove legacy routes, delete unused code paths, and decommission jobs. Track “legacy surface area” as a metric and review it regularly, just like you would track bugs or latency.
Conclusion
The strangler fig approach works because it respects what legacy systems really are: critical business assets with hidden complexity. By replacing the system one slice at a time, you lower risk, learn continuously, and keep delivering value throughout the modernization.
If you want this to succeed, treat routing and safety nets as first-class work, choose slices with clear seams, and make deletion of legacy components an explicit, scheduled outcome. Incremental does not mean indefinite.