The Migration Strategy Framework
Big-bang migrations are usually disasters. Incremental migrations work. Strangler fig, branch by abstraction, parallel run, feature flags.
The Migration Strategy Framework
In one line: Big-bang migrations are usually disasters. Incremental migrations — strangler fig, branch by abstraction, parallel run, feature flags — work.
The temptation with a painful old system is to "just rewrite it from scratch." That almost never works: the rewrite always takes longer than planned, the original system keeps adding features in the meantime, and switching cost is huge. The reliable approach is to migrate piece by piece, with traffic that you can flip back the moment something breaks.
Successful migration patterns
Strangler fig pattern (named after the Ficus aurea tree that grows around a host tree, eventually replacing it):
- Build new system alongside old.
- Route some traffic to new system (start small).
- Gradually shift more traffic.
- Eventually retire old system.
Branch by abstraction:
- Introduce an abstraction layer over the thing you want to replace.
- Both old and new implementations satisfy the abstraction.
- Gradually migrate callers to use the new implementation.
- Remove the old one when nothing uses it.
Parallel run:
- Both old and new systems do the work.
- Compare results; alert on differences.
- When you trust the new system, retire the old.
Feature flag rollout:
- New code path behind a flag.
- Enable for 1% → 10% → 100% over time.
- Easy rollback (flip the flag).
Anti-patterns
Big-bang rewrite:
- "We'll spend 6 months rebuilding from scratch."
- Original system continues to accumulate features during the rewrite.
- Project always takes longer than planned.
- Switching cost is huge.
- Almost always disastrous.
Stop-the-world:
- "We'll freeze feature work for 3 months to migrate."
- Product team revolts.
- Customers churn.
- Engineering credibility damaged.
Rules for migrations
- Never let old and new diverge for long. Sync as you go.
- Have a kill switch. Be able to roll back instantly.
- Measure progress. "We're 80% migrated" should mean something specific.
- Communicate clearly. Other teams need to know what's happening.
- Commit to finishing. Half-migrated systems are worse than either old or new.
The old billing system is a tangle of cron jobs and stored procedures. The team decides to migrate to a clean, event-driven service.
Their plan (using strangler fig + parallel run + feature flags):
- Build the new billing service in parallel. Mirror writes to it from the old system.
- Have the new service emit invoices in shadow mode — no customer impact, just compared to the old output.
- Once shadow output matches old for 4 weeks, flip 1% of customers to the new service's invoices via feature flag.
- Watch error rates and customer support tickets. Ramp to 10%, 50%, 100% over six weeks.
- Old code is removed only after 30 days of 100% rollout with no issues.
Total migration: 4 months — slower than a "big bang" plan would have estimated, but actually finished and with no billing incidents. A big-bang version of the same migration almost certainly would have had a billing outage.
The single worst migration outcome isn't "old system stays" or "new system ships." It's "both run forever, neither is canonical."
Half-migrated systems double maintenance cost, double the surface area for bugs, and create a perpetual question of "which one is the truth?" If you start a migration, commit a date to finishing it. If you can't commit, don't start.
Common mistakes
- Starting a migration with no committed finish date. "We'll get to the rest later" is how you end up in the half-migrated trap that's worse than either old or new. Before the first PR lands, write down the date by which the old system is deleted — not just unused, deleted — and hold yourself to it.
- Treating shadow mode and parallel run as the destination. Running old and new side-by-side is a tool to build confidence, not a steady state. If you've been "running both" for more than a quarter, you've stopped migrating and started doubling your maintenance burden. Pick a side.
- Building a kill switch you never actually test. Rollback plans rot fast — a flag that flips back to "old" stops working the moment the old code path drifts. Exercise the rollback at least monthly during the migration, or assume it's broken when you need it most.
- Migrating a system you don't fully understand yet. Strangler fig works because you can see what the old system does at each callsite. If the legacy system has uncatalogued behaviors (cron jobs, side effects, hidden consumers), discovery happens during the cutover — which means outages. Map first, migrate second.
Page checkpoint
Did migration strategy stick?
RequiredWhat's next
→ Continue to The "Two Versions of the Same Code" Principle — when duplication is a bug and when it's actually fine.