Free lesson · Microservices Interview
Migration Strategies Overview - Strangler Fig, Branch by Abstraction, Parallel Run
Migration Strategies Overview - Strangler Fig, Branch by Abstraction, Parallel Run
Migrating from a monolith to microservices is the most common system design question for senior and principal-level candidates. It is also the question where the gap between theoretical knowledge and production experience is most visible. Anyone can name the Strangler Fig pattern. Very few candidates can walk through the mechanics of a phased migration, explain the data synchronisation challenges at each phase, and describe the rollback strategy when phase three fails.
This module gives you that depth. We start with an overview of three migration strategies - Strangler Fig, Branch by Abstraction, and Parallel Run - then dedicate individual lessons to the hardest sub-problems: data migration, sequencing decisions, organisational change, and anti-patterns.
This overview lesson compares the three strategies side by side so you can choose the right one for a given situation. The next lesson dives deep into the Strangler Fig, which is the most common strategy and the one you are most likely to use in an interview.
Why Migration Is Harder Than Greenfield
Before examining the strategies, it is worth understanding why migration is fundamentally harder than building microservices from scratch.
In a greenfield system, you design the service boundaries, data models, and communication patterns on a clean slate. There is no existing data to migrate, no existing users to keep online, no existing integrations to maintain, and no existing team structure to reorganise. You can make all the right decisions because there are no legacy constraints.
In a migration, every decision is constrained by what already exists:
- The data is already there. Millions of rows in a shared database that must be split across service-owned databases without downtime, data loss, or inconsistency.
- Users are already online. The system must continue serving traffic throughout the migration. You cannot take it down for a week while you reorganise.
- Integrations already exist. External systems, partner APIs, third-party services, and internal tools all depend on the monolith's current interfaces. They cannot all be updated simultaneously.
- Teams already exist. Engineers have existing responsibilities, existing knowledge of the monolith's codebase, and existing habits. Restructuring teams around services is an organisational change that affects people's daily work and career paths.
These constraints mean that migration is not a technical problem with a technical solution. It is a sociotechnical problem that requires managing technology, data, people, and risk simultaneously over months or years. Every migration strategy that follows in this lesson addresses the technical dimension, but the organisational and data dimensions - covered later in this module - are equally important and often more difficult.
Important: Migration is a process, not an event. There is no moment when the monolith becomes microservices. There is a long period during which both the monolith and the new services coexist, traffic is gradually shifted, data is synchronised between old and new systems, and the monolith shrinks as services take over its responsibilities. Planning for this coexistence period - and all the complexity it creates - is what separates realistic migration plans from theoretical ones.
Strategy 1 - The Strangler Fig Pattern
The Concept
Named after the strangler fig tree that grows around a host tree, gradually replacing it, the Strangler Fig pattern incrementally replaces monolith functionality with microservices. Rather than rewriting the monolith, you build new services alongside it and gradually redirect traffic from the monolith to the services until the monolith has no remaining responsibilities and can be decommissioned.
The Mechanics
The key architectural element is a routing layer (sometimes called a strangler facade) that sits in front of both the monolith and the new services. The routing layer receives all incoming requests and decides whether to forward each request to the monolith or to a new service.
Initially, 100 percent of traffic goes to the monolith. As each feature is extracted into a service, the routing layer is updated to send that feature's traffic to the new service. Over time, more and more traffic is handled by services, and the monolith's responsibilities shrink until it handles nothing.
graph TD
subgraph Phase1["Phase 1: Start"]
C1["Clients"] --> R1["Router"]
R1 -->|"100%"| M1["Monolith"]
end
subgraph Phase2["Phase 2: First Extraction"]
C2["Clients"] --> R2["Router"]
R2 -->|"Feature A"| S2["Service A"]
R2 -->|"Everything else"| M2["Monolith"]
end
subgraph Phase3["Phase 3: Multiple Extractions"]
C3["Clients"] --> R3["Router"]
R3 -->|"Feature A"| S3A["Service A"]
R3 -->|"Feature B"| S3B["Service B"]
R3 -->|"Feature C"| S3C["Service C"]
R3 -->|"Remaining"| M3["Monolith\n(shrinking)"]
end
This diagram shows three phases of the Strangler Fig migration. In Phase 1, all traffic flows through a router to the monolith. In Phase 2, Feature A is extracted to a new service while everything else stays in the monolith. In Phase 3, Features A, B, and C are handled by separate services while the monolith handles the shrinking remainder.
Risk Profile
Low risk per step. Each extraction is small and scoped. If the new service for Feature A has a problem, the routing layer can redirect Feature A traffic back to the monolith immediately. The rollback is a configuration change, not a code revert.
Long duration. A full migration using Strangler Fig takes months to years, depending on the monolith's size and the number of features. The coexistence period is long, and both the monolith and the services must be maintained simultaneously.
Data synchronisation complexity. During the coexistence period, the monolith and the new services may both need access to the same data. This creates synchronisation challenges that are the hardest part of the migration (covered in Topic 3).
When to Use
Strangler Fig is the default migration strategy. Use it when the monolith is large, the risk tolerance is low, and the migration needs to happen without downtime. It works for almost any migration scenario, which is why it is the most commonly referenced strategy in interviews.
Strategy 2 - Branch by Abstraction
The Concept
Branch by Abstraction introduces an abstraction layer inside the monolith that allows old and new implementations to coexist within the same codebase. The abstraction acts as a switch: the monolith calls the abstraction, and the abstraction delegates to either the old implementation or the new implementation (which may be a call to an external microservice).
The Mechanics
The process has four steps:
-
Create the abstraction. Identify the component to be extracted. Create an interface (or abstract class) that represents the component's API. Refactor the monolith to call the interface instead of the concrete implementation.
-
Implement the new version. Build the new implementation - either a new module within the monolith or a call to an external microservice. The new implementation conforms to the same interface.
-
Switch traffic. Use a feature flag or configuration setting to switch from the old implementation to the new implementation. The switch can be gradual (percentage-based rollout) or instantaneous.
-
Remove the old implementation. Once the new implementation is verified in production, remove the old code and the abstraction layer (if it is no longer needed).
graph TD
subgraph Monolith["Inside the Monolith"]
Code["Calling Code"] --> Abs["Abstraction Layer\n(Interface)"]
Abs -->|"Feature flag: old"| Old["Old Implementation\n(Monolith code)"]
Abs -->|"Feature flag: new"| New["New Implementation\n(Calls microservice)"]
end
New --> MS["External\nMicroservice"]
This diagram shows Branch by Abstraction inside a monolith. The calling code talks to an abstraction layer. A feature flag controls whether the abstraction delegates to the old implementation within the monolith or to a new implementation that calls an external microservice.
Risk Profile
Very low risk. The abstraction layer means both implementations exist simultaneously. Switching between them is a feature flag toggle, not a deployment. Rollback is instant.
Requires monolith modification. Unlike Strangler Fig, which wraps the monolith externally, Branch by Abstraction requires changing the monolith's code to introduce the abstraction layer. This means you need the ability (and the confidence) to refactor the monolith.
Codebase complexity during transition. The monolith temporarily contains both the old implementation and the new abstraction-based implementation. This increases code complexity during the transition period.
When to Use
Branch by Abstraction works best when the component being extracted is deeply embedded in the monolith - when there is no clean HTTP endpoint that a routing layer can intercept. If the component you want to extract is called internally by many parts of the monolith through method calls, Branch by Abstraction lets you introduce the switch point at the call site rather than at the network boundary.
It is also useful when you want to test the new implementation alongside the old one within the same process before incurring the cost of a network boundary. You can run both implementations, compare their outputs, and build confidence before splitting them into separate services.
Strategy 3 - Parallel Run
The Concept
Parallel Run sends each request to both the monolith and the new service simultaneously. The monolith's response is returned to the user (it is the source of truth), and the new service's response is captured and compared. Discrepancies are logged and investigated.
This is not a migration strategy by itself - it is a verification strategy that is often used as a phase within a Strangler Fig or Branch by Abstraction migration. The parallel run builds confidence that the new service produces the same results as the monolith before traffic is fully switched.
The Mechanics
The routing layer (or abstraction layer) receives each request and:
- Forwards it to the monolith and waits for the response
- Simultaneously forwards it to the new service
- Returns the monolith's response to the user
- Compares the two responses asynchronously
- Logs any discrepancies for investigation
The comparison runs in the background and does not affect the user experience. The monolith remains the source of truth throughout the parallel run period.
Risk Profile
Lowest risk of all strategies. The user always gets the monolith's response. The new service is exercised with real traffic but its output is never served to users until the team is confident in it.
Double the resource cost. Every request is processed twice - once by the monolith and once by the new service. This doubles the compute cost for the parallel-run period. For high-traffic systems, this cost can be significant.
Not suitable for write operations. Parallel Run works well for read operations (both systems can process the same query). For write operations, running both creates the risk of duplicate side effects - an order placed in both the monolith and the new service, a payment charged twice. Write operations require more careful handling, typically with the new service processing in a "dry run" mode that does not commit side effects.
When to Use
Parallel Run is most valuable for high-risk migrations where correctness is critical - financial calculations, billing systems, pricing engines, compliance-sensitive operations. The cost of a discrepancy is high enough to justify the double resource cost of running both systems.
It is also valuable as a confidence-building phase within a larger migration. "We ran both systems in parallel for two weeks, processed 4 million requests, and found zero discrepancies" is a compelling argument for switching traffic to the new service.
For write operations, the parallel run strategy requires adaptation. One approach is "shadow writing" - the new service processes the write request but does not commit it to its database. Instead, it logs what it would have written and compares that against what the monolith actually wrote. This catches logic discrepancies without creating duplicate data. Another approach is to run the parallel write against a separate shadow database that can be compared against the monolith's database through batch reconciliation jobs. Both approaches add complexity but are essential for migrating write-heavy systems with correctness guarantees.
GitHub famously used a variation of Parallel Run when migrating their database layer. They ran both the old and new code paths simultaneously, compared the results, and only switched to the new path after verifying equivalence across millions of operations. The technique added weeks to the migration timeline but eliminated the risk of data inconsistency.
Comparing the Three Strategies
| Dimension | Strangler Fig | Branch by Abstraction | Parallel Run |
|---|---|---|---|
| Where the switch happens | External routing layer | Internal abstraction layer | External routing + comparison |
| Monolith modification required | No (external wrapper) | Yes (introduce abstraction) | No (external wrapper) |
| Rollback mechanism | Route traffic back to monolith | Toggle feature flag | N/A (monolith is always primary) |
| Risk per step | Low | Very low | Lowest |
| Resource cost | Normal (one system handles each request) | Normal | Double (both systems process every request) |
| Duration | Months to years | Weeks to months per component | Days to weeks (verification phase only) |
| Best for | Large-scale incremental migration | Deeply embedded components | High-risk correctness verification |
| Write operations | Handled naturally | Handled naturally | Requires dry-run mode |
| Data synchronisation | Required during coexistence | Required during coexistence | Required during coexistence |
Tip: In interviews, name the strategy and explain why you chose it. "I would use the Strangler Fig pattern because the monolith has clear HTTP endpoints that a routing layer can intercept, and the team needs to migrate incrementally without downtime" is a strong answer. "I would rewrite the system" is a red flag that suggests you have never migrated anything in production.
Combining Strategies
In practice, most migrations combine elements of all three strategies rather than using one exclusively.
A typical combination looks like this:
- Strangler Fig as the overall migration framework - a routing layer that gradually redirects traffic from the monolith to new services
- Branch by Abstraction for components that are deeply embedded in the monolith and cannot be intercepted at the routing layer - internal business logic, shared utilities, deeply-coupled data access code
- Parallel Run as a verification phase for high-risk extractions - running both systems on production traffic to build confidence before switching
The combination is the realistic answer for interviews. "For the external-facing API endpoints, I would use a Strangler Fig approach with routing-layer traffic shifting. For the internal pricing engine, which is called by multiple parts of the monolith through internal method calls, I would use Branch by Abstraction to introduce an interface and gradually shift to the new pricing service behind a feature flag. Before fully switching, I would run a parallel run for a week to verify that the new pricing service produces identical results."
Interview: Knowing when to combine strategies is the principal-level skill. L5 candidates name the Strangler Fig pattern. L7 candidates explain which parts of the migration use which strategy and why. The combination answer demonstrates that you understand each strategy's strengths and limitations well enough to compose them.
What Comes Next
The next lesson takes the most common strategy - Strangler Fig - and examines it in detail. Step-by-step mechanics, routing layer design, the dual-running period, rollback procedures, and the specific challenges that emerge at each phase of the migration.