Every developer has stared at a codebase and thought: "If I could just start over, I'd do this right." It's one of the most seductive ideas in software. It's also one of the most dangerous.
The rewrite fantasy
The old code is messy. The architecture made sense three years ago but not anymore. There are hacks layered on hacks. Variable names that make no sense. Functions that do six things. You open a file and feel physically uncomfortable.
So you pitch the rewrite. "Give me three months and I'll rebuild this properly. Modern stack, clean architecture, full test coverage." Management agrees. You start fresh. A blank canvas. It feels incredible.
Then reality shows up
Week one is euphoric. Everything is clean. The new architecture is elegant. You're moving fast because there's no legacy code to work around.
Week four, you hit the first edge case. The old code handled it with a weird if statement that made no sense. Now you understand why it was there. You add your own weird if statement.
Week eight, you're reimplementing features that already worked. Not building anything new — just getting back to where you were. The business hasn't gained a single feature in two months.
Week twelve, the rewrite is "almost done" but missing a hundred small things the old system handled that nobody documented. The deadline slips. Then slips again.
The old code knows things you don't
That ugly codebase you want to throw away? It contains years of bug fixes, edge case handling, and hard-won knowledge about how the real world works. Every weird hack exists because a real user hit a real problem and someone fixed it.
When you rewrite from scratch, you throw all of that away. You will rediscover every single edge case the hard way — in production, with real users, on a Friday evening.
When a rewrite is actually justified
I'm not saying rewrites are never the answer. Sometimes they are:
- The tech is dead. If your app runs on a framework that's abandoned with no security patches, migration is a survival move.
- The domain changed completely. If the product pivoted so hard that the old architecture literally can't support what you need, incremental changes won't cut it.
- It's small enough. Rewriting a 500-line service is fine. Rewriting a 50,000-line monolith is a different conversation entirely.
But even in these cases, I'd rather strangle the old system gradually than burn it down and start over.
The strangler fig approach
Instead of rewriting everything at once, replace pieces one at a time. Build the new system alongside the old one. Route traffic to the new version for one feature. When it works, move the next feature. Keep going until the old system has nothing left to do.
It's slower. It's less satisfying. But it works. You're shipping improvements to users every week instead of disappearing into a cave for six months and hoping for the best.
Refactor, don't rewrite
Most code that feels like it needs a rewrite actually needs a refactor. Extract a function. Rename a variable. Break a god class into three smaller ones. Add tests around the scary part, then change it safely.
Boring? Yes. Effective? Extremely. The best engineers I've worked with don't dream about rewrites. They make the existing code a little better every time they touch it. Over months, the codebase transforms — without ever breaking.
Resist the rewrite. Improve what's there. Ship something new instead.