Blog / Legacy Modernization Software Development

We Rewrote It from Scratch. Here's What We Learned.

The rewrite is the most seductive idea in software engineering. It's also one of the most dangerous. We've done it. We've watched others do it. Here's what actually happens.

11 Jul 2026 · 6 min read

Joel Spolsky wrote about it in 2000. He called it "the single worst strategic mistake that any software company can make." He was talking about the decision to rewrite software from scratch. Twenty-five years later, engineering teams are still making the same mistake — with the same results.

I understand why. I've felt the pull myself.

You're working in a codebase that's ten years old. The original architects are gone. The tests are sparse, or worse, they exist but nobody trusts them. Every new feature requires touching four files you don't fully understand, and every deployment comes with a low-grade anxiety that something unrelated will break. The system works — just barely, in ways nobody can fully explain — and everyone knows it.

In that moment, the rewrite feels obvious. Clean it all up. Do it right this time. Start fresh.

Here's what actually happens.


The first six months feel great.

The new codebase is clean. The architecture makes sense. The team is energised. Progress feels faster because you're building without the accumulated weight of the old system's decisions.

This phase is real. It's also misleading.

What you're doing in this phase is rebuilding the parts of the system you understand — the documented features, the happy paths, the functionality that's visible in the UI. You're not rebuilding the parts you don't understand yet: the edge cases, the workarounds, the undocumented business logic that lives in code comments from 2016, the behaviours that were added in response to specific customer requests that nobody wrote down.

Those parts are coming. You just don't know it yet.


Then you hit the first "why does it do this?" moment.

Somewhere around month eight, a user reports that a calculation is wrong in the new system. You check the old system. The old system does it differently. You try to find out why the old system does it that way. There's no documentation. The person who built it left three years ago. The git blame leads to a commit that says "fix calculation" with no further explanation.

You make a decision — the old system was probably wrong, the new approach is more logical — and you move on.

Six weeks later, you find out that the old calculation was correct for a specific tax jurisdiction that three of your largest customers operate in. The "fix" in the new system has been calculating incorrectly for those customers for a month and a half.

This happens. Not once — many times. Every legacy system of any complexity contains years of accumulated business logic that isn't written down anywhere except in the code. When you rewrite the code, you lose access to that logic. You rebuild the parts you can see and you discover the parts you missed when users tell you something is wrong.


The timeline doubles. Then it doubles again.

The original estimate for the rewrite was nine months. At nine months, you're at 70% feature parity with the old system — if you're measuring the features you knew about at the start. The missing 30% is almost entirely edge cases and undocumented behaviour that you've discovered along the way.

The revised estimate is three more months. Then two more. The old system is still running because you can't cut over yet. Now you're maintaining two systems simultaneously. Every feature addition has to happen in both. The team that was energised at the start is exhausted. The business is asking why they're still paying for both systems.

This is not a planning failure. It's a fundamental property of the problem. Legacy systems are complex because they've absorbed years of complexity from the real world. That complexity doesn't disappear when you start a new repository. It waits for you to rediscover it.


What to do instead.

The alternative isn't to accept the legacy codebase as permanent. It's to modernise it incrementally rather than replacing it wholesale.

The pattern most commonly associated with this approach is called the strangler fig — named after a vine that grows around an existing tree, gradually replacing it while the tree continues to support it. In software terms: identify the parts of the system that most need modernisation, extract them piece by piece into a new implementation, and route traffic to the new version while the old version still handles everything else.

This is slower in the early stages. It's less emotionally satisfying than starting fresh. But it has a property that rewrites don't: at every point in the process, the system works. You're not holding the business hostage to a completion date. You're delivering improvements continuously while maintaining the behaviour users depend on.

It also forces you to understand the existing system rather than abandon it. When you extract a component from a legacy codebase and reimplement it, you have to understand what that component actually does — including the undocumented edge cases. You discover the business logic by engaging with it, not by ignoring it and hoping you recreate it correctly from memory.


When the rewrite is the right call.

I want to be honest: sometimes a rewrite is the right answer. If the technology choice is genuinely unsuitable — if the system is built on something so outdated that incremental improvement is effectively impossible — the calculus changes. If the codebase is so tangled that extraction is harder than replacement, you may have no practical alternative.

But the bar for this should be high. Not "the old code makes me uncomfortable." Not "we could do it better today." The question is whether incremental improvement is genuinely impossible, or just slower and harder than starting fresh.

Most of the time, it's slower and harder. And most of the time, that's still the better choice.


The systems that have survived for ten years survived because they work. They're full of decisions you wouldn't make today — and also full of decisions you'd make again if you understood the full context. The goal of modernisation is to keep the latter and replace the former. That's harder to do than deleting the repository and starting over. It's also more likely to produce a system that still works ten years from now.

If you're looking at a legacy codebase and wondering what the right path forward is, we're happy to talk through it. We've been on both sides of this problem.

All articles Book a Free Call