- / ai-assisted
- / building
- / diagnoses
The Rewrite Was Never the Problem
The conventional wisdom that rewrites kill companies was directionally correct, but for the wrong reason. Taste was the binding constraint, not cost. The cost has collapsed. The constraint hasn't.
For most of the last twenty years, there has been a piece of received wisdom in software: rewrites kill companies. Don't rewrite. Iterate. Refactor in place. The rewrite is the bet-the-company decision that costs you the eighteen months your competitor used to ship.
I've never fully agreed with that. But I do think it was directionally correct, for the last twenty years.
It was correct for a reason that didn't get said out loud. Rewrites are not intrinsically bad. What made them dangerous was that the cost of doing one was high enough to bet the company on, and the taste required to do one well was in short supply. Either ingredient on its own is survivable. Together, they kill.
One of those ingredients has now collapsed.
Syntax is free. Porting something, rewriting something, rearchitecting something, used to be a multimillion-pound project for any non-trivial system. It is now something you can do in the span of a couple of afternoons. The contractor I would have paid £200 an hour to rewrite a service is now a £200-a-month Claude subscription that handles three or four rewrites in a week.
What hasn't collapsed is the taste required to know which rewrites are good ideas and which aren't.
So the conventional wisdom is wrong now, but it's wrong in a specific way. Rewrites no longer kill companies because of cost. They can still kill companies because of judgment. The bet-the-company part of the decision is gone. The taste part is still there, and it matters more, not less.
Telling the messes apart
The taste that matters is the taste of distinguishing accidental complexity from essential complexity. Fred Brooks did the heavy lifting on the distinction in No Silver Bullet in 1986; it has not gotten less true.
Software accumulates mess for two reasons.
The first is that people are messy. They implement things less than ideally because they were tired, or didn't have time, or didn't quite see the right shape. Some of the mess in any non-trivial codebase is that kind of mess. It's avoidable. It's not carrying any real knowledge about the problem. Rewrite it and you lose nothing.
The second is that the problem itself is messy. The contours you're building inside are intrinsically difficult and irregular, and the messy code you ended up with is a faithful reflection of how messy the underlying problem actually is. Rewrite that kind of mess and you get code that reads beautifully but is wrong. It encodes a version of the problem that's tidier than the real one. Every weird-looking thing in the old version was the old version remembering something the new version has forgotten.
Distinguishing those two kinds of mess is the taste in question. Get it right and a rewrite is pure gain. Get it wrong and you've thrown away knowledge the system was holding for you, and you won't notice the loss until your users do.
When the rewrite was for the wrong reason
The rewrites I have watched kill companies were almost always done for academic reasons rather than for a pressing limitation. The architecture wasn't elegant. The stack was old. A new team came in and didn't like the patterns the previous team had used. None of those are bad observations on their own. They just aren't the kind of thing whose absence is hurting the business.
The actual answer in most of those situations was get more people in the door using it. Distribution was the bottleneck. The architecture wasn't. But distribution is hard and uncomfortable, and rewriting a codebase is, in some sense, easier. You control the entire process. The success criteria are internal. You can feel productive every day. Rewriting is what people reach for when they can't bring themselves to do the harder thing.
The rewrites that worked, in contrast, were aimed at something the team had been bumping into for months. Not a deal-breaker on any single instance, but more than a nice-to-have. A recurring friction the product was paying for in user-visible costs, even if no individual instance of it would have justified the spend on its own.
The example sitting on my desk
I have a recent one of my own. For the past six months I have been starting every new web project the same way: two or three hours of scaffolding before I touched anything that actually mattered for the project itself. Auth. A service layer. Migrations. The Docker setup. The deployment story. Every new tool I built carried a small portion of those hours in pure setup cost, and every one was almost identical to the last.
That repetition was accidental complexity in the Brooks sense. The scaffolding wasn't carrying any project-specific knowledge. The actual contours that mattered for any given tool (the data, the user flow, the validation rules) were essential complexity, and they changed every project.
So I extracted the scaffolding. The result is two repositories, Flatpack and Baseplate, that handle the parts of every new tool that don't carry per-project knowledge and leave the parts that do for whoever's building. Six months ago that extraction would have been a small but real project. With current LLMs in the loop, it was three weekends. The rewrite cost dropped by an order of magnitude. The call about what to extract and what to leave bespoke was the same call I'd have had to make twenty years ago.
What changes when the rewrite becomes routine
I've long believed in building one to throw away. That's an old principle (Brooks again, since we are already in his world). The first version of anything you build is going to be wrong about something important; the question is how cheaply you can find out what.
The principle was always right, but it was bounded by what a throwaway version actually cost. When the throwaway cost a month of an engineer's time, you did it for things worth a month. When it costs an afternoon, you do it for things worth an afternoon, and the universe of things worth doing expands.
The same logic now applies to rewrites of things you have already shipped. When the rewrite cost a year and a million pounds, the universe of acceptable rewrites was small, and don't rewrite was the correct default. When the rewrite costs two afternoons, the universe of acceptable rewrites is large, and rewrite the things you have been bumping into for months is the correct default.
What stays the same is the taste call. The rewrite that is worth doing is the one that addresses a real, recurring, user-visible friction. The rewrite that goes wrong is the one that mistakes essential mess for accidental mess, ports a tidied version, and loses something the messy version had been quietly carrying.
That part hasn't changed. It got more important, not less.
The future of software is more rewrites, more throwing away, more rebuilding. The output gets more resilient, more scalable, more useful as a result. There is no excuse anymore to not ship something new. The discipline is the same one it has always been. The cost barrier is gone.