There's a famous piece of advice that says you should never rewrite your software from scratch. I read it years ago, nodded along, and quoted it to other people like I'd earned it.
Then I did the exact thing it warned against. I deleted four years of code and started over.
It was the most reckless engineering decision I've made. It was also one of the best. The truth, like most truths in this job, lives somewhere uncomfortable in the middle.
A full rewrite is almost always a mistake — except when the original codebase is actively blocking every change you need to make. If you do it, the win isn't cleaner code. It's the knowledge you gained the first time, finally applied without the scar tissue.
The short version:
My app started as a side project. The kind you build in a weekend and accidentally turn into a business. Every decision in those first months was made by a person who didn't know what the app would become — because nobody does at the start.
By year three, adding a feature felt like defusing a bomb. One change in the billing logic could break the onboarding flow for reasons no one could explain. We had three different ways of handling dates. The "temporary" workaround from 2022 was load-bearing.
I wasn't dealing with messy code. I was dealing with code that had stopped telling the truth about what the system did.
That's the real signal. Not ugliness. Not old patterns. A codebase that lies to you about its own behavior is a codebase you can no longer reason about — and software engineering is mostly reasoning. Learning to read a system honestly, the way I describe in the brutal truth about becoming a senior developer, is what separates a justified rewrite from a vanity one.
Photo by Ilya Pavlov on Unsplash
My first instinct was wrong, and I want to be honest about it.
I wanted to rewrite because the code was ugly. Old patterns. A framework version two majors behind. Naming conventions from a younger, more optimistic me.
None of that is a reason to rewrite. Ugly code that works and ships is worth more than beautiful code that doesn't exist yet. If I'd rewritten purely for aesthetics, I'd have spent six months reproducing the same features with nicer indentation and a worse track record.
The reason that actually justified it was different: the architecture had become a tax on every future decision. Adding anything cost three times what it should. That's leverage math, not taste.
Here's the test I use now before any big rewrite:
| Reason to rewrite | Valid? |
|---|---|
| The code is ugly / old style | No |
| A newer framework looks nicer | No |
| I'd understand it better if I wrote it | No (you wouldn't) |
| Every new feature fights the architecture | Yes |
| Core assumptions baked in are now wrong | Yes |
| Nobody can safely change critical paths | Yes |
If your reasons live in the top half, you don't want a rewrite. You want a weekend of refactoring and a good night's sleep.
The single most valuable thing I owned going into the rewrite wasn't code. It was understanding. I knew the domain. I knew which edge cases were real and which were imaginary fears. I knew which "flexible" abstractions had never flexed once in four years.
That knowledge is the entire reason a rewrite can win. The second version isn't better because I'm a better programmer — though I am. It's better because I finally knew what I was building before I built it.
A few specific lessons that stuck:
The first version teaches you what to build. The second version is where you finally get to build it.
The classic rewrite death is the "big bang" cutover. You hide in a cave for a year, emerge with the new system, flip the switch, and discover the old app did forty things you forgot about. The business stalls. Morale craters. Sometimes the company doesn't survive the gap.
I did the opposite. The old app stayed in production, fully functional, the entire time. The new system grew alongside it, taking over one slice at a time. Auth first. Then the read-only dashboards. Then, much later, the scary billing paths.
This is slower and far less heroic. It's also the only version of a rewrite I'd ever recommend. Every week, something was shipping. Every week, the new system carried a little more real traffic. There was never a single terrifying switch — just a long, boring migration that nobody outside the team really felt.
Photo by Alexandre Debiève on Unsplash
I leaned hard on a few things to keep the rewrite from eating me alive:
None of this is glamorous. All of it is why the project shipped.
The hardest parts of a rewrite aren't technical. They're emotional, and they ambush you when you least expect it.
There's a long, demoralizing middle where the new system isn't done and the old one still runs everything. You're pouring weeks into a thing that, to the outside world, produces nothing visible. No new features. No flashy demos. Just a quiet machine slowly absorbing the work the old one already does. People ask what you're building and you don't have a good answer.
That stretch is where most rewrites die — not from a technical wall, but from morale. The team loses faith. Leadership gets nervous. Someone suggests just bolting the new feature onto the old system "for now," and the rewrite quietly becomes a third codebase to maintain.
The way I survived it was by making progress visible on purpose. Every migrated slice got announced. Every flag we flipped to route real traffic to the new system, we celebrated, however small. We tracked the percentage of traffic on the new path like a scoreboard. It sounds silly. It kept the project alive.
If you take one thing from this section: a rewrite is a morale project disguised as an engineering project. Budget for the feelings, not just the functions.
If this kind of hard-won engineering judgment is what you're after, it's worth following along as I work through more of these decisions one honest post at a time.
Q: How do I know if I should rewrite or refactor? Try to refactor first, always. If you can make the change you need by improving the existing code incrementally, do that. Only consider a rewrite when the architecture itself — not the code style — is blocking nearly every change. Most "we need a rewrite" situations are actually "we need a focused month of refactoring."
Q: Won't I just make all the same mistakes again? You'll make new ones, which is progress. The old mistakes you understand now, so you'll avoid the big ones. Keep a written list of "things the old version got wrong" and check every architecture decision against it.
Q: How long does a rewrite really take? Longer than you think, and that's the point. If your estimate doesn't scare you a little, you've underscoped it. Budget for double, ship incrementally, and never set a single hard cutover date.
Q: Is it worth it for a small app? Sometimes a small app is the best candidate, because the blast radius is tiny and you can do it in a weekend. The danger scales with size. The smaller the system, the cheaper the lesson.
The advice "never rewrite" isn't wrong. It's just incomplete. The full version is: never rewrite for vanity, never rewrite in one big bang, and never rewrite away the knowledge you paid for the first time.
Do it for leverage. Keep the lights on. Let the old version be the teacher and the new version be the student that finally listened.
If you're staring at a codebase that fights you on every change, ask yourself one honest question before you reach for the delete key: am I trying to escape the work, or finally do it right? The answer tells you everything.
No following, no network, no luck. Just an unglamorous system I ran for eighteen months. Here's exactly what I did.

I went from 200 to 11,000 subscribers without hiring anyone. AI didn't write my newsletter — it did everything around it.

I chased big, audacious goals for years and burned out every time. Then I built my whole life around wins so small they felt like cheating.

Comments
Sign in to join the conversation
No comments yet. Be the first to share your thoughts!