The system runs the business and everyone is afraid of it. The developer who built it left in 2019. The vendor stopped patching it. Every new feature takes a quarter, every deploy is a ceremony, and somewhere in the back of your head is the date when the thing it runs on goes end-of-life.
The instinct is the big rewrite: freeze the old system, build the new one next to it, cut over on a weekend, champagne. That plan has a name in the industry — it's called the way projects die. Eighteen months in, the new system covers 70% of what the old one does, the business has kept changing, and now you're maintaining two systems while shipping zero features.
There's a better way, and it's boring on purpose.
The strangler fig, in plain English
A strangler fig is a tree that grows around another tree, replaces it limb by limb, and eventually stands on its own. That's the pattern: you don't replace the legacy system, you surround it.
Concretely:
- Put a thin routing layer in front of the old system — a reverse proxy, an API gateway, sometimes just DNS.
- Pick one capability. Build only that in the new stack.
- Route that one slice of traffic to the new code. Everything else still hits the old system.
- Repeat until the old system handles nothing, then turn it off.
The business never stops. There is no big-bang cutover, no weekend of prayer. Each slice is small enough to reverse if it misbehaves — you flip the route back and you're exactly where you were yesterday.
Migrate data in the right order
Most modernization pain isn't code, it's data. The old system's database is the real legacy: undocumented columns, implicit rules, twenty years of edge cases stored as rows. The order of operations matters more than the technology.
Reads first, writes last.
- Mirror the data. Stand up the new database and sync it from the old one — change-data-capture if the old system supports it, scheduled jobs if it doesn't. The old system remains the source of truth.
- Point reads at the mirror. New features and carved-out slices read from the new database. If the sync has gaps, you find out from a stale report, not a corrupted order.
- Dual-write the first workflow. When a slice needs to write, write to both systems and compare. Disagreements are bugs in your understanding of the old system — every one you catch here is an incident you didn't have.
- Flip the source of truth for that slice only. The old system now mirrors from the new one for that table, or stops caring about it entirely.
Teams that invert this — migrate writes first because "that's where the value is" — are choosing to debug data corruption in production. Reads are rehearsal. Rehearse.
Parallel-run: the safety net that actually works
For anything that computes something — invoices, payroll, commissions, eligibility — don't trust tests alone. Run both systems on the same real inputs and diff the outputs.
A parallel run is mechanical: every night, feed yesterday's real transactions through the new code, compare its answers to what the old system actually did, and review every mismatch. At first, the mismatch report is long; most of it will be the old system being wrong in ways the business has silently absorbed for years. You'll have to decide, case by case, whether to replicate the old behavior or fix it — that decision log becomes the most valuable document of the whole project.
When the diff report has been empty for two or three weeks of real volume, you cut over. Not because a test suite passed — because the new system already did the job, in production conditions, and nobody could tell the difference.
When a rewrite actually beats a refactor
Rarely. But not never. A ground-up rewrite is the right call only when most of these are true:
The honest summary: rewrite small, well-understood, decoupled things. Strangle everything else. The systems people most want to rewrite — big, load-bearing, mysterious — are precisely the ones where a rewrite fails.
The 90-day modernization plan
You don't need a two-year roadmap to start. You need 90 days of disciplined groundwork.
Days 1–30: understand and de-risk. Inventory what the system actually does — not what the docs say, what the logs say. Get it into version control if it isn't. Get a staging environment that matches production. Add monitoring so you know what normal looks like. No new code yet; this month pays for the next twenty-four.
Days 31–60: build the seam. Stand up the routing layer in front of the system with 100% of traffic still passing through to the old code. Stand up the new database mirror and let the sync run. Ship the deploy pipeline for the new stack. Nothing user-visible has changed, and that's the point — you've built the machinery of replacement without betting anything on it.
Days 61–90: carve the first slice. Pick that low-risk, visible capability and move it: route its traffic to new code reading from the mirror. Parallel-run anything it computes. Demo it. By day 90 you have proven the pattern end to end, and every slice after this one is repetition, not invention.
From there it's a drumbeat — a slice every few weeks, each one shrinking the legacy footprint, none of them capable of taking the business down. Modernization stops being a project with a terrifying go-live date and becomes a habit with a burndown chart.
The big rewrite asks the business to hold its breath for two years. The strangler fig never asks it to hold its breath at all.
Legacy modernization is core full-stack work for us, usually paired with an integration layer that keeps the old and new systems honest while both are alive — and a maintenance retainer so the new system never becomes the next legacy. Got a system everyone's afraid to touch? Tell us what it runs, and we'll map the first slice.
Read next:
- Build vs buy: a framework for custom software
- What custom software actually costs in 2026
- How to scope an MVP that ships in six weeks
Related: