Skip to content
· 7 min read

Migrate, do notrewrite

Why we have spent seven years talking clients out of full rewrites, and what we do instead.

A building wrapped in scaffolding mid-construction

Every few months someone asks us why we have spent years arguing against rewrites. The short answer is that we keep meeting the consequences of them. This is the longer one.

The seduction of the clean slate

A rewrite is the most appealing project in software. It has a start, an end, and a definition of done that everyone can point at. The existing system is visibly broken, which makes the case unanswerable in a meeting. And there is a deep structural bias toward overestimation: the new thing only has to contain the features you know about, not the ones that only surface once customers depend on them.

Two decades of edge cases do not appear in your requirements document. They appear in production, usually at 2am, usually the week after you thought you were finished.

The rewrite will be faster than fixing it — right up until the month it is not.

What a migration actually looks like

The alternative is not "never touch it". It is characterisation, a routing layer, and incremental replacement. Concretely, on a recent engagement for a retail bank:

  1. Characterise before changing. We wrote tests against the existing behaviour first. Not to keep it forever — to make every subsequent change visible. Without this, you cannot tell a refactor from a regression.
  2. Put a facade in front. A routing layer that could send any request to either the legacy core or a new service. From that day, migration was a configuration change rather than a deployment.
  3. Move one capability at a time, behind a flag, with the ability to revert in seconds. Fourteen of them over eleven months.
  4. Retire the duplicate. The worst outcomes come from systems that run in parallel forever because nobody owns the decommission.

The economics

The team's instinct was to rewrite. What actually happened, measured against the baseline we captured in week one:

  • Release lead time went from one day to forty minutes.
  • P95 page load went from 4.8 seconds to 0.9.
  • Infrastructure cost fell 64%.
  • Deploy frequency went from twice a month to thirty times a month.

Eleven months, no planned downtime, no customer-visible incidents. A rewrite would have taken roughly the same time and delivered a fraction of it, with considerably more risk.

When a rewrite is right

We are not dogmatic. Sometimes the existing system genuinely cannot be evolved: the hardware it depends on is discontinued, the licence cannot be renewed, or the domain has changed so much that the old model actively misrepresents the business. The test we apply is simple — can we put a facade in front of it and move one capability at a time? If yes, migrate. If the answer is no because the thing cannot be isolated at all, then rewrite, but budget for the discovery you skipped.

The part people forget

The hardest part of a migration is not technical, it is organisational. There is always a migration in progress, and a team splitting its attention between the thing that pays the bills and the thing that does not. That is why the facade has to be genuinely incremental. Nine months of "nearly there" is worse than a year of steady, visible progress — because nine months of nearly-there is when everyone quietly starts looking for another job.

Related work

This is the theory. Here is the practice.

The same thinking, applied to a real engagement with measured outcomes.