Most software is not replaced because it broke. It is replaced because changing it became more expensive than starting again — and that point arrives long before anyone admits it.
The interesting question is not why systems decay. It is why the decay is so consistently invisible until it is terminal.
The pattern
It starts well. The first release is quick, because there is nothing to be careful about. The second is quick too. Somewhere around the fourth or fifth, things slow down, and nobody can say exactly why. Estimates get longer. Small changes have surprising consequences. Developers start prefacing sentences with “it depends what else it touches.”
Eventually someone proposes a rewrite. Not because anyone can point at the problem, but because the alternative — understanding the existing system well enough to change it safely — feels harder than starting again.
The cost was not incurred at the rewrite. It was incurred years earlier, in a hundred decisions that each saved an afternoon.
What actually makes software hard to change
Decisions duplicated instead of stated once. A rule about who may see what, written into eleven places. Changing it means finding all eleven. Missing one is a bug you will discover from a customer.
No boundaries. When any part can reach into any other, a change is never local. Boundaries feel like overhead when a system is small, and they are the only reason a large one stays workable.
Assumptions that never got written down. Currency is always the same. Every customer has one address. Nobody has two accounts. Each is true when written and false three years later, and each is buried in code nobody remembers writing.
Nothing that tells you when you break something. Without tests, every change is a judgement call about consequences nobody can fully trace. Teams respond rationally: they stop changing things and start adding things beside them.
Data shaped for the first version. The structure of your data outlives every other decision. Get it wrong and you spend years working around it in code.
The warning signs, in order of appearance
•Estimates for similar work grow over time rather than shrinking.
•The same bug returns in a new place.
•Only one person can safely change a particular area — and everyone knows who.
•Small changes require testing unrelated features.
•New developers take months, not weeks, to become useful.
•Someone says “we should probably rewrite this” and nobody argues.
The first is the earliest and the most ignored, because it is easy to attribute to the work getting harder rather than the system getting worse.
What designing for change actually means
It does not mean building for imagined futures. Speculative flexibility is its own kind of waste — abstractions built for requirements that never arrive, making today's work harder for a tomorrow that goes elsewhere.
It means something narrower:
•State each decision once, in a place with a name, so changing it is one edit rather than a search.
•Draw boundaries where the business has them. Billing and scheduling are different concerns; if your system cannot tell, every change to one risks the other.
•Make the data model outlast the interface. Interfaces get redesigned; data structures are inherited by everything that follows.
•Write down what must stay true, as tests. Not for coverage — so that a future change that breaks an assumption announces itself instead of shipping.
•Leave the reasoning behind. Code says what it does. It rarely says why, and why is what the next person needs.
None of this is exotic. All of it is skipped under deadline, which is precisely how the cost accumulates.
What it is worth
There is a real trade-off, and pretending otherwise is how people are sold gold-plating. Building carefully takes longer in the first months. If a product is genuinely disposable — a campaign, an experiment, a thing with a known end date — do not build it to last. Build it fast and throw it away as intended.
The mistake is applying disposable thinking to something you intend to keep. Most of the expensive rebuilds we are asked to look at began as something someone meant to replace within the year, five years ago.
If you are already there
A full rewrite is the most expensive option and the most likely to fail, because it commits to reproducing behaviour nobody has documented while the old system keeps changing underneath it.
Usually there is a better path: find the boundary that hurts most, isolate that one area, replace it behind a stable interface, and let the rest keep working. Repeat. It is slower to describe and considerably faster to survive.
It also has the advantage of delivering value continuously, rather than asking a business to fund eighteen months of work that produces nothing visible until the end.
If a system you depend on is getting harder to change, we are happy to look at it and tell you honestly whether it needs replacing or just needs boundaries — the second answer is more common, and much cheaper.