Skip to content
QSWEQB
hardDesignCase StudySeniorStaffLead

How do you replace a system that cannot be switched off?

Put a routing facade in front of the old system, migrate one business capability at a time, and treat dual-write as a reconciliation problem rather than two inserts: name a writer of record at every moment, shadow-compare before flipping reads, and sequence slices so value lands early.

6 min readUpdated 2026-07-26

What the interviewer is scoring

  • Does the candidate slice the migration by business capability rather than by technical layer
  • Whether dual-write arrives with a divergence and reconciliation story rather than as two writes in a transaction
  • That they can name the writer of record at every point in the migration, and say what event moves it
  • Whether value is sequenced to land during the programme instead of accumulating behind one cutover
  • Can they describe how the old system is finally switched off, and who pays for the deletion

Answer

The two facts that set the shape

Before any pattern, establish two things, because everything else follows from them. First, what the old system is the source of truth for, and who else reads that truth. Second, what the business is unwilling to lose for even an hour. A ledger that feeds regulatory reporting and a product catalogue that feeds a website are both "legacy", but only one of them makes you design for provable equivalence rather than eventual convergence.

The reason to lead with this is that candidates who open with "strangler fig" have named a routing pattern, not a plan. The routing is the easy half. The difficulty lives in the data, in the callers you have not discovered yet, and in the political question of who funds the last twenty percent of a programme after the interesting part has shipped.

Slice by capability, and never by layer

The unit of migration is a business capability with a boundary a stakeholder recognises: quote generation, payment capture, statement production. Each slice must be independently switchable and independently revertible, which means it must have its own routing decision, its own data ownership answer, and its own success measure.

The failure alternative is slicing by layer — new UI over the old core, or new API over the old database. That feels like progress and buys nothing, because the thing you wanted to retire is still the thing making every decision. Worse, a layer slice cannot be reverted independently, so you have created a big-bang cutover with extra hops in front of it.

The facade earns its place by being boring

Put a routing component in the request path early, before it has any routing to do, and make its first deployment a pass-through to the old system. That gets the risky change — a new component in the critical path — into production while the blast radius is zero, and it gives you the seam every later slice needs.

Three properties are worth stating explicitly. The routing decision must be data-driven and changeable without a deploy, so that a flip and a revert are the same size of action. It must be able to route by attribute, not just by percentage, because "all traffic for tenants in this segment" is how you keep the first slice inside a population that has agreed to be first. And it must emit which way each request went, or you will spend the whole programme unable to answer why a customer saw two different answers.

The dotted shadow write and the two edges converging on the comparison are what make this a reconciliation design rather than only a router.

flowchart LR
    C[Callers] --> F[Routing facade]
    F -->|remaining traffic| L[Legacy system]
    F -->|migrated slice| N[New service]
    L --> LD[(Legacy store)]
    N --> ND[(New store)]
    N -.->|shadow write| LD
    LD --> X[Async comparison]
    ND --> X
    X --> Q[Mismatch buckets with owners]

Dual-write is a reconciliation project

Writing to both systems is not a migration technique on its own. It is a decision to create divergence and then a commitment to detect and repair it. The design question is which system is authoritative while both are being written, because the answer decides what happens on partial failure.

StageWriter of recordReads served byWhat failure means
Shadow writeOldOldNew-system write fails: log and continue. Nothing customer-visible.
Compared writeOldOld, new compared asynchronouslyMismatch is a defect ticket, not an incident.
Read flipOldNewDivergence is now customer-visible; revert is a routing change.
Write flipNewNewOld system is a backfill target only, or frozen.
DecommissionNewNewNothing writes to the old store; access is revoked, then it is deleted.

Three things make this survivable. Every write carries an idempotency key generated at the edge, so a retry into either system cannot double-apply. The comparison is asynchronous and continuous, not a one-off verification at cutover, and it classifies mismatches into buckets — genuine data loss, known model differences, and timing windows — because an undifferentiated mismatch count cannot be driven to a decision. And the reconciliation output is an artefact somebody reads, not a dashboard nobody owns.

A weekly reconciliation report that a programme actually uses looks like this, and being able to sketch it is worth more than reciting the pattern:

BucketCountTrendOwnerDisposition
Rounding on multi-currency totalssteadyflatPaymentsAccepted, documented as a known model difference
Missing address line 2fallingimprovingMigrationMapper defect, fix in flight
Records present in new, absent in oldnon-zerorisingMigrationBlocks read flip until root-caused
Timing window under 5 secondshighflatExpected; excluded by comparison window

Note what the fourth column does. Every bucket has a named owner and an explicit disposition, so "we still have mismatches" can never be used either to block a flip indefinitely or to wave one through.

Sequencing so value lands before the end

A migration justified only by the eventual decommission has no defenders when priorities shift, and priorities always shift. Order the slices so each one delivers something a stakeholder would have paid for anyway, and say what that is before you start.

That usually means going after the capability where the legacy constraint is most expensive rather than the one that is easiest to lift. If pricing changes take six weeks because they need a mainframe release, migrating pricing first buys the business a visible reduction in change lead time in the first quarter. The easiest slice — usually a read-only lookup — is technically sensible as a rehearsal but produces nothing anyone outside engineering notices, which is exactly the situation in which a programme gets paused.

Two sequencing rules hold up under challenge. Do the first slice for the rehearsal value and pick it small deliberately, but do the second slice for the business value and pick it painful deliberately. And schedule at least one decommission early, even a trivial one, so the organisation sees that switching things off is a thing this programme does.

Where these programmes die

The interesting failure is not a botched cutover. It is a programme that reaches ninety percent, declares the new system live, and then runs both for three years because the last callers were never found and nobody would fund the search. You end up paying for two systems, two on-call rotations, and a reconciliation job in perpetuity, having achieved a strictly worse architecture than either endpoint.

Guarding against it is unglamorous and specific. Instrument the old system's inbound traffic and data access from day one so the caller inventory comes from observed reality rather than from documentation, and re-run that inventory before every flip. Treat the decommission as funded scope with a date and an owner, in the same plan as the build, not as a tidy-up phase. And insist on a written definition of done that includes revoked credentials and deleted data, because an old system that is merely idle will be quietly reconnected by someone under deadline pressure within a year.

A migration is only finished when the old system is gone, so plan the deletion at the same time as the first slice — and make sure something the business wanted has already shipped long before you get there.

Likely follow-ups

  • Shadow comparison shows a small but persistent mismatch rate. How do you decide whether that blocks the read flip?
  • Writes into the old system arrive from three batch jobs whose owners have left. What do you do?
  • How do you migrate history when the two data models disagree about what a customer is?
  • Funding is halved twelve months in. What do you cut, and what must survive?

Related questions

Further reading

strangler-figlegacy-migrationdual-writereconciliationcutover