DSME Global Links
Engineering

Escaping the legacy trap: modernizing systems without a big-bang rewrite

The full rewrite is the most seductive and most fatal proposal in software. How strangler-fig migrations let you replace a legacy system piece by piece — while the business keeps running on it.

Muhammad Dayyan·Founder & CEO·July 12, 2026·8 min read
Escaping the legacy trap: modernizing systems without a big-bang rewrite

Every aging system eventually produces the same meeting: the codebase is unmaintainable, nobody wants to touch it, and someone proposes the clean solution — rewrite it properly this time. It is the most seductive proposal in software, and one of the most reliably fatal. The rewrite starts, the legacy system keeps changing because the business cannot stop, and the new system chases a moving target for two years before someone quietly cancels it.

The alternative is not living with the mess forever. It is replacing the system incrementally — piece by piece, in production, while the old one keeps serving customers. It is less glamorous than a rewrite and dramatically more likely to finish.

Why big-bang rewrites fail on schedule, every time

The rewrite's fatal flaw is structural, not a matter of execution. A legacy system is not just code — it is fifteen years of accumulated business rules, edge cases and undocumented behavior that customers depend on without anyone remembering why. The rewrite team must rediscover all of it, and every rule they miss becomes a production incident on cutover day.

Worse, the value delivery curve is all wrong. An incremental migration ships improvement every month; a rewrite delivers nothing until it delivers everything. For its entire duration the business pays two teams — one maintaining the old system, one racing to catch it — and gets zero new capability. That math holds only if the rewrite finishes on time, and the moving target ensures it doesn't.

The strangler fig: route, replace, retire

The pattern that works is named after a fig that grows around a host tree until the tree is gone. You place a routing layer — an API gateway, a reverse proxy, sometimes just a façade in code — in front of the legacy system. At first it passes everything through. Then, one capability at a time, you build the replacement, flip its routes to the new service, and watch. The legacy code path is still there; rollback is a config change, not a crisis.

The order of replacement is a strategic choice. Start with something low-risk but real to prove the plumbing — an edge capability with clean boundaries. Then prioritize by pain: the modules that change most often, block the most roadmap, or hurt the most to maintain. The stable, boring corners of the legacy system go last — or never, which is fine. A strangler migration does not owe anyone completeness; it owes the business a system it can change again.

The data is the hard part — plan it first

Teams plan the code migration in detail and hand-wave the data, and the data is where incremental migrations actually get hard. While both systems run, they must agree on state — which means for every entity you migrate, you need an explicit answer to one question: which system owns the truth right now?

The workable patterns are well-worn. Keep the legacy database as the source of truth initially and have the new service read from it or a synced replica. When a capability fully moves, ownership of its data moves with it, and the sync reverses direction for anything the old system still needs. During transitions, dual-write with reconciliation — write to both, compare continuously, alert on drift — is nerve-wracking but honest. What does not work is pretending the two systems can each own half an entity indefinitely; every long-running migration we have rescued had that mistake somewhere at the bottom of it.

Characterize the old behavior before you change it

Legacy systems rarely have tests, and their documentation describes the system someone intended to build, not the one running. Before replacing a module, pin down what it actually does: capture real production inputs and outputs and turn them into characterization tests — assertions that the replacement produces what the original produced, oddities included.

This is also where the routing layer pays double. Because all traffic passes through it, you can shadow-run the new implementation — send it a copy of live requests, compare its answers against the legacy system's, and log every divergence — for weeks before it serves a single real response. Most divergences turn out to be legacy bugs users have unknowingly adapted to. Each one forces a valuable decision: preserve the quirk or fix it deliberately, with the business in the room.

Keep the effort honest

Incremental migrations fail differently than rewrites: not with a cancelled project but with a permanent twilight where both systems run forever and the migration becomes background noise. Guarding against that is a management discipline, not a technical one.

The end state is worth stating plainly: not a perfect system, but a system the business can change again at reasonable cost and risk. Modernization is finished when the legacy system stops being the reason you say no to things — and with the strangler approach, parts of it start saying yes months in, not years.

  • Every migration slice ships to production and takes real traffic — a replacement that isn't live is a rewrite in disguise
  • Retire legacy code the moment its replacement is stable; two live implementations of one behavior is a standing incident risk
  • Freeze new feature work on legacy modules that are next in line — build new features on the new side only
  • Track one number visibly: share of traffic (or transactions) served by the new system — a stalled number is an early warning
  • Time-box the twilight: if a slice has been dual-running for months, either finish it or consciously decide that module stays legacy
M
Written by
Muhammad Dayyan
Founder & CEO, DSME Global Links