Skip to main content
Algo Vortex

Software

Legacy system modernization

Full rewrites fail often enough that they should be the last option, not the first. The systems that get modernised successfully are the ones replaced one piece at a time while the old one keeps running.

By Umar HayatChief Technology Officer, Algo Vortex

Key takeaways

Big-bang rewrites usually fail

Two years of no new features while a moving target is reimplemented from incomplete knowledge. Something always changes underneath.

Old is not the same as broken

A system that works, is understood, and is cheap to run does not need modernising. Fix what costs you something.

Strangle it, do not replace it

Put a boundary in front, move one capability at a time behind it, and keep both running until the old one is empty.

The knowledge is the risk

The requirements live in code nobody documented and in the heads of two people. Recovering them is most of the work.

Why do full rewrites fail so often?

Because you are reimplementing a system whose requirements nobody wrote down, while the business keeps needing changes to the version you are replacing. Both halves of that sentence are fatal on their own.

The requirements problem is the deeper one. A legacy system that has run for a decade encodes thousands of decisions, most of them undocumented and some of them load-bearing in ways nobody remembers. That odd conditional in the pricing code is there because a large customer negotiated something in 2016. Reimplementing from a specification written today reproduces what people currently believe the system does, which is a subset of what it actually does, and the difference shows up as production incidents after cutover.

The moving target problem is more mundane and just as effective. A rewrite takes eighteen months to three years. The business does not stop during that time. Either you freeze features, which nobody tolerates for long, or you implement every change twice, which slows both efforts and doubles the divergence.

Then there is the political failure. A multi-year project with no user-visible output is difficult to defend through a budget cycle, a leadership change, or a bad quarter. Many rewrites are not cancelled because they failed technically. They are cancelled because they ran out of sponsorship.

Should you modernise at all?

Often not. Old is not a problem by itself. A system that works, that someone understands, that runs cheaply and rarely breaks, is a system doing its job regardless of what it was written in. Modernising it because the technology is unfashionable is spending real money on an aesthetic preference.

The reasons that justify the work are concrete. You cannot hire for it, and the people who maintain it are approaching retirement. It cannot do something the business now needs and no reasonable extension gets you there. It is a security or compliance liability, running on unsupported infrastructure with no patch path. It costs materially more to run than the alternative. Or a change that should take a day takes a month, and you can point at the evidence.

Write down which of those applies before starting. If none of them does, or if the answer is that the code is unpleasant to work in, that is a refactoring conversation rather than a modernisation programme.

Be honest about the second-order costs too. A modernisation programme consumes the team's capacity for eighteen months. The features that do not get built during that period are part of the price.

  • Reason

    Cannot hire for the stack

    Justifies the work

    Yes

    Why

    Maintenance risk with a deadline attached

  • Reason

    Unsupported, unpatchable

    Justifies the work

    Yes

    Why

    Security and compliance exposure

  • Reason

    Blocks a needed capability

    Justifies the work

    Yes

    Why

    Direct business cost, measurable

  • Reason

    Change takes weeks not days

    Justifies the work

    Yes, with evidence

    Why

    Compounding delivery drag

  • Reason

    Expensive to run

    Justifies the work

    Sometimes

    Why

    Compare against migration cost

  • Reason

    Old technology

    Justifies the work

    No

    Why

    Not a problem on its own

  • Reason

    Unpleasant to work in

    Justifies the work

    No

    Why

    That is refactoring, not modernisation

How does incremental modernization actually work?

Put a boundary in front of the legacy system first. A facade, a gateway, an API layer, whatever fits. Everything that uses the old system goes through the boundary instead of talking to it directly. Nothing has been modernised yet, and this step alone is often several months of work on a system with many direct consumers. It is also what makes everything after it possible.

Then move one capability at a time behind the boundary. Pick a piece with clear edges, reimplement it as a new service, and route that traffic to the new implementation while everything else continues hitting the old system. Callers do not know anything changed. If the new piece is wrong, you route back.

Run both in parallel for a while on the pieces that matter. Send the same request to both, compare the answers, and log the differences. This is how you recover the undocumented requirements: the old system tells you what it actually does, in production, on real data, and the differences are exactly the decisions nobody wrote down. It is the single most valuable technique in this whole exercise.

Repeat until the old system has nothing left to serve, then turn it off. It might take two years, the same as a rewrite would, but you are in production the entire time, you can stop at any point with value delivered, and no single step is large enough to fail catastrophically.

What do you do about the data?

This is usually the hardest part and the one that gets underestimated. A legacy database has decades of accumulated reality in it: fields repurposed for a second meaning, records that violate constraints added later, dates stored as text in three formats, and encoding artefacts from a migration in 2011.

Profile it before planning anything. Actually query for nulls in required fields, duplicate keys, orphaned references, and values outside their supposed range. The results routinely surprise the people who have owned the system for years, and every surprise is scope.

Decide per entity whether the new system owns the data, the old one does, or you sync. Two systems writing the same records is the failure mode to avoid, because the reconciliation becomes unsolvable. Clean ownership per entity, even if it means an awkward intermediate period, is worth the awkwardness.

Migrate in stages with the ability to reverse. Copy, verify by comparing counts and checksums, run the new path in read-only mode against migrated data, then switch writes. And keep the old data available in a readable form long after cutover, because someone will ask a question about 2019 that only the old system can answer.

How do you recover the knowledge?

Start with the people, urgently, especially if anyone is close to leaving. Sit with them and record the sessions. Ask why rather than how, because the how is in the code and the why is not. The odd exception in the discount logic, the batch job that must run before another one, the customer with special handling: none of that is written down anywhere.

Read the code as the specification, because it is the only complete one that exists. Prioritise the parts with the most unusual logic, since that is where the undocumented business rules concentrate. Clean straightforward code rarely holds surprises.

Use production behaviour as the tie-breaker. When the code and the documentation and the person's memory disagree, which happens regularly, the running system is right by definition. Parallel-run comparison is how you access that answer at scale.

Write it down as you go, in a form that will survive. The documentation produced during a modernisation is often more valuable than the new code, because the new code will be replaced again eventually and the business rules will not.

What should you do first?

Pick the piece that is both painful and separable. Not the biggest, not the most interesting, and not the core. Something with clear boundaries that causes real trouble today, so that finishing it produces a visible improvement and buys you credibility for the next piece.

Deliver something in the first quarter. A modernisation programme with nothing to show after three months is a programme that will be questioned at six and cancelled at nine. Early visible wins are not a political game, they are how the work survives long enough to finish.

Keep the boundary discipline throughout. The most common way an incremental modernisation turns into a stalled rewrite is a new service reaching directly into the legacy database because it was faster that week. Once that happens the pieces are coupled again and you have two systems instead of one improved one.

API integration services covers the boundary and reconciliation engineering in more depth, and how to build custom software covers the slice-based approach generally.

Next step

Sitting on something nobody wants to touch?

Tell us what it does, what it blocks, and who still understands it. We will propose a first separable piece and say plainly when leaving it alone is the better call.

Talk to Algo Vortex

Live products where this kind of work showed up in the build.

RelayHub product screenshot

Twilio + OpenAI inbox automation

RelayHub started from a blunt observation: phone and chat should not live in separate tools. Sales and support kept losing the thread when a caller switched to SMS or a chat widget. The brief was one shared inbox. Twilio traffic and digital messages land together. AI clears the routine work so people only jump in when judgment matters. Teams also needed to steer the assistant without shipping a new build every time the script changed. Admin-controlled prompts per contact group were in the brief from day one. File digests mattered too. Long PDFs and call notes piled up unread. The product needed a path from upload to a short summary the whole group could scan before the next shift. Nobody on the project believed every reply should be fully automated. Refund fights, tone-sensitive replies, and messy exceptions still need a human. RelayHub uses OpenAI to draft, summarize, and clear the easy queue so senior staff spend time on work that actually needs them.

RouteMind product screenshot

AI fleet advisor + live load board

RouteMind exists so shippers and carriers can see loads, capacity, and routes in one place. Dispatch should cost less time and fewer wasted miles. The product pairs a live load board with an AI Fleet Advisor. Planners match freight to available trucks and compare paths with real map data instead of gut feel. Empty miles and stale boards were the business pain. When capacity is a guess, trucks deadhead and fuel burns for no revenue. Status, distance, and advisor guidance had to show up in the tools dispatchers already live in. Another spreadsheet export at the end of the shift was not going to cut it. Dispatchers needed advice that respected current capacity, not a generic logistics chatbot. The Fleet Advisor had to read live loads and vehicle state, then suggest moves a planner could accept or reject in the same UI. RouteMind was never meant to replace judgment. It was meant to cut the time spent assembling the picture before judgment starts.

Questions

More on all insights, custom software, or contact Algo Vortex.

Want to talk through a build?

Need a dedicated team or a clear project plan? We match engineers to your stack and put a first plan on the calendar.

Get in touch
Book a call