Skip to content
Sothen

August 2, 2026 · 5 min read

All articles

Stabilize or rewrite a stuck system

Rewriting looks like the clean way out and almost never is. The signals that separate a system needing repair from one that genuinely needs replacing.

Blog

A technician repairs the exposed mechanism of an old typewriter on a workbench.

At some point in the life of every system that succeeded, someone says in a meeting that it is beyond saving and the best move is to start from scratch.

The idea often gains momentum with people who have not yet lived through all of that system's exceptions. It is often wrong. Sometimes it is the only responsible decision. The difference between the two cases is worth a great deal of money.

Why rewriting is seductive

The current system is known in every one of its defects. The new system does not have any yet. The comparison is between something real and something imagined, and the imagined one wins every time.

There is also a very common misreading. Ugly code is not sufficient proof of incompetence. A good share of those strange conditions, exceptions and patches may be the sedimented record of real cases that turned up in the business over the years: the customer with two tax IDs, the month the tax rule changed, the report that has to balance a particular way because the accountant insists. When nobody documented those decisions, the code may be the last place where they still exist.

Rewriting from scratch means throwing that record away and betting you will rediscover all of it, in production, with the customer complaining.

And there is the cost nobody puts in the estimate: during a rewrite you operate two systems. The old one cannot stop evolving, because the business does not stop. Every new rule has to be built twice. That is why rewrites slip in such a characteristic way: they are chasing a moving target.

The signals that genuinely justify a rewrite

There are few of them, and none is "the code is bad".

The data model is wrong at the root. Code can be refactored gradually; a wrong data model contaminates everything that touches it. If the fundamental structure represents the business badly, every new feature costs more than the last, and no amount of code cleanup fixes it. That is the case when the entity that should exist doesn't, or when two different things were merged into one.

The platform has reached the end. A language or framework with no security updates, an abandoned core dependency, a server nobody can reproduce any more. This is not an engineering preference, it is an expiry date.

The cost of change keeps rising. This is one of the most objective signals: take one class of change, the kind that comes up constantly, and compare effort, risk and the number of affected areas over recent months. In a healthy system, the team learns and makes recurring changes more predictable. When equivalent changes become more expensive even for people who know the system, there is a structural problem to investigate.

Nobody can run the system outside production. If there is no environment where you are allowed to be wrong, there is no safe repair. Either you create that environment, or it is fair to admit this is not going to work.

The signals that say stabilize

These are recognizable too. The system has no tests, but the data model makes sense. It is slow, but it is correct. It is frightening to touch, and that fear is concentrated in two or three known areas rather than across the whole thing. There is no documentation, but someone who knows is still around.

In all of those cases the problem is confidence, not foundation. And confidence is rebuilt in a specific order.

The right order to stabilize

Almost every botched rescue starts by touching the code. The order that works is different.

First, see. Error logging, metrics, alerts. Before any change, you need to know what is happening today, including the problems that already exist and nobody reports. Without that, you will not know whether your change made things better or worse.

Second, be able to go back. Automated deployment and rollback in minutes. A system where shipping a change is a risky event is a system nobody will risk changing, and it freezes out of fear.

Third, fence before you touch. Tests only around the part you are about to change, not across the whole system. Full coverage of a legacy system is a project that never ends; coverage in the neighbourhood of one change is a few days' work and gives back the nerve to make it.

Only then, change. By this point, what looked like a doomed system usually looks like a neglected one.

The third path, which is the honest one

In most hard cases the right answer is neither of the two.

You build the new system around the old one and migrate function by function. The first new capability is born outside the old system, talking to it. Then the second. Gradually the old one sheds responsibilities until it is a shell, and one day it is switched off without ceremony.

It is slower to announce and faster to deliver value, because each step ships on its own. There is no big launch day, which is precisely the day rewrites fail. And it preserves the option to stop halfway: if after three migrated functions the rest of the old system is running fine, it is perfectly acceptable to leave it there.

How we approach it

When an existing product reaches us, the first step is not to propose a rewrite. It is to understand why it stopped moving forward, where the cost of change sits and what is worth preserving.

Sometimes the best recommendation is to stabilize before building anything new. It is far cheaper to find that out at the beginning than in the middle of a long rewrite.