Skip to content
VALUEARCTECHNOLOGIES Start a project
← BLOG
ENGINEERING·12 AUG 2026·9 MIN

Why we stopped rewriting legacy systems

A rescue beats a rewrite in most cases we see. Here is the decision tree we use, and the two conditions that make us change our minds.

ARUN M.

Every few weeks someone sends us a system they have inherited and describes it the same way: nobody understands it, it breaks on Fridays, and the person who wrote it left in 2021. The request that follows is almost always "rewrite it."

We usually say no, and then we usually end up doing the work anyway — just not as a rewrite. Here is the reasoning, because it is not sentimentality about old code.

The rewrite maths are worse than they look

A rewrite is not "build the same thing again." It is build the same thing again, including the parts nobody documented, while the old system keeps changing underneath you, and while the business keeps needing the features it was promised this quarter.

Three costs get left out of the estimate almost every time.

The undocumented behaviour is the product. A ten-year-old billing module is not messy because the previous team was careless. It is messy because it encodes the tax rule from 2019, the one client who is invoiced on the 7th, and the refund path that only fires for cancellations inside 24 hours. That is not technical debt. That is the business, written down in the only place it was ever written down.

You pay for parallel running. Until cutover, two systems exist. Both need changes. Both need people. The "freeze" that was supposed to last six weeks lasts seven months, and by month four the new system is behind the old one on features.

The riskiest day is at the end. A rewrite concentrates all its risk into a single cutover, which is the worst possible shape for risk. A rescue spreads it across many small releases, each of which can be reverted on its own.

What we do instead

We call it a rescue, and it has a fixed order.

Week one: read it, do not touch it. We instrument before we change anything. Which endpoints actually get traffic? Which cron jobs have not fired successfully in a year? Which tables are written to and never read? On one travel platform we found that 40% of the routes had not been hit in six months. That 40% did not need rewriting, porting, or testing. It needed deleting, which is much cheaper.

Get a test around the money path first. Not full coverage — that is a multi-year goal and a bad first milestone. One characterisation test around the thing that moves money or sends a customer something. It does not have to be pretty. It has to fail when the behaviour changes.

Then put a seam in. Pick the boundary that has the most pain behind it and the least coupling across it. Route new traffic to new code behind a flag, keep old code serving the rest, and compare outputs in production before switching. This is the strangler pattern and it is boring on purpose.

Fix the operational floor before the features. Structured logs, error tracking, a runbook, a staging environment that resembles production. Teams often want to skip this because it is invisible to the business. It is the reason the next six months are calm instead of frantic.

The two conditions that change our answer

We are not dogmatic. Two things make a rewrite the right call.

The platform is a dead end. Not "old" — dead. The runtime no longer receives security patches, the hosting provider has an end-of-life date, or the framework's last release was years ago and its ecosystem has moved on. When staying put means shipping known vulnerabilities, incrementalism has run out of room. Note that this is a fact you can check, not an opinion about code quality.

The data model contradicts the business. If the schema says one customer has one address and the business now sells to companies with forty branches, every feature from here is a workaround stacked on a workaround. You can migrate a schema incrementally, but past a certain distance between what the model says and what the business does, you are writing a new system inside the old one and paying the old one's overheads for the privilege.

Everything else — bad naming, no tests, a framework two majors behind, a language the current team dislikes — is an argument for investment, not replacement.

What this looks like to a client

Slower on paper for the first month and faster from about month three. There is no demo at the end of week two, because week two is spent reading logs. What there is instead: a system that stops waking someone at 2am, then a system that can take a feature without three days of archaeology first.

We run this on our own products too. Travel CRM's quotation engine is on its third internal architecture and has never been rewritten from scratch, because it has been earning money throughout — and a system that is earning money is a system whose behaviour is worth preserving exactly.

MORE FROM THE BUILD