Code3 All articles
Engineering Culture

Burn It Down and Start Fresh: The Case for the Strategic Rewrite

Code3
Burn It Down and Start Fresh: The Case for the Strategic Rewrite

There's a line engineers cross at some point in every long-lived codebase. You open a file to fix what should be a two-line bug, and three hours later you're staring at a tangled mess of workarounds, deprecated dependencies, and comments like // DO NOT TOUCH THIS — 2019. You fix the bug. You ship it. You feel vaguely ashamed. And then you do it all again next sprint.

This is the trap of perpetual patching — and a lot of teams stay in it way longer than they should.

The conventional wisdom in software development is that rewrites are dangerous. Joel Spolsky famously called rewriting from scratch "the single worst strategic mistake that any software company can make." And honestly? He wasn't wrong about the failure mode. But he was writing in 2000, and the tooling, deployment infrastructure, and team practices we have now make the calculus look pretty different.

So let's talk about when a rewrite is actually the right call — and how to execute one without blowing up your product in the process.

The Hidden Tax of Keeping Bad Code Alive

Most engineering leaders can tell you their velocity has slowed. Fewer can tell you exactly why. The answer, more often than not, is accumulated technical debt that's been patched so many times it's structurally unsound.

Think about what perpetual patching actually costs you. Every new feature takes longer because developers have to understand a labyrinth of legacy behavior before touching anything. Onboarding new engineers is a months-long ordeal. Testing is brittle because the original architecture didn't anticipate the scale you're operating at now. And the senior engineers who understand the system best are spending their time babysitting old code instead of building new things.

This isn't a hypothetical. Stripe's API infrastructure team has publicly discussed the compounding costs of maintaining backward compatibility across years of layered decisions. Shopify has talked about the work involved in decomposing their Rails monolith — not because Rails is bad, but because the original architecture couldn't support the scale they'd grown into. In both cases, the decision to invest in structural change (rather than more patches) unlocked shipping velocity that incremental fixes never could have.

The real cost of legacy code isn't the code itself — it's the opportunity cost of everything your team isn't building.

Refactor vs. Rewrite: How to Actually Decide

Here's a framework worth keeping in your back pocket. Ask these three questions before committing to either path:

1. Is the problem architectural or implementational?

If your code is slow, buggy, or hard to read but the underlying data model and system design are sound, refactoring is probably the right move. You're fixing the implementation, not the foundation. But if the core assumptions baked into the system no longer reflect reality — your data model doesn't match your domain, your service boundaries make no sense, your API contracts are a historical accident — then refactoring is rearranging deck chairs.

2. What's the blast radius of change?

A rewrite makes the most sense when the system in question is relatively self-contained — a specific service, a data pipeline, a frontend layer — rather than the entire product. "Rewrite everything" is almost always the wrong answer. "Rewrite this bounded component and expose it behind the same interface" is often very achievable.

3. Can you run old and new in parallel?

The strangler fig pattern — where you build the new system alongside the old one and gradually migrate traffic — is the single most important risk-mitigation tool for rewrites. If your architecture allows for it (and most modern architectures do), you can ship the new version incrementally, validate it against real production data, and decommission the old code only when you're confident. This is how you avoid the "big bang rewrite" failure mode.

ROI Math: Making the Business Case

Engineering leaders often struggle to get organizational buy-in for rewrites because the benefits are diffuse and the costs are upfront. Here's how to sharpen the argument.

Start by measuring your current maintenance overhead. What percentage of sprint capacity goes to keeping existing systems stable versus building new features? If that number is above 40%, you have a problem that compounding interest will make worse over time — not better.

Next, estimate the acceleration you'd get post-rewrite. Even conservative assumptions tend to look good. If your team ships features 30% faster after a rewrite and you have ten engineers at $180K fully-loaded cost, that's significant leverage.

Finally, factor in risk. Legacy systems fail in ways that are expensive and unpredictable. A major outage in a poorly-understood codebase can cost more in engineer-hours, customer trust, and SLA penalties than a well-scoped rewrite ever would.

Put those numbers in a doc and walk your stakeholders through them. "We're going to spend three months rebuilding this so we can ship twice as fast for the next three years" is a story most product leaders can get behind — especially if you can point to a comparable example.

What Good Rewrite Execution Actually Looks Like

A few principles from teams that have done this successfully:

Scope ruthlessly. The rewrite should have a clear boundary. You're not rebuilding the product — you're rebuilding a specific component. Write down what's in scope and what isn't, and enforce that boundary.

Keep shipping during the rewrite. This is non-negotiable. If the rewrite becomes a reason to stop delivering value to users, you'll lose organizational support fast. The strangler fig approach lets you do both.

Define done before you start. What does success look like? Matching feature parity? Hitting a performance benchmark? Reducing oncall incidents by a measurable amount? Without a clear definition of done, rewrites expand indefinitely.

Celebrate the decommission. When you finally delete the old code, make it a moment. Engineers who've been maintaining a painful legacy system for years deserve to feel that win. It also signals to the org that the investment paid off.

The Third Time Really Is the Charm

There's a useful heuristic that circulates in engineering circles: the first time you build something, you're figuring out the problem. The second time, you're solving the problem you wish you'd had. The third time, you actually know what you're doing.

Most legacy systems that need rewrites are second-time solutions — they were built when the problem was better understood than at the start, but before the team had the scale, the tooling, or the architectural maturity to build it right. The rewrite isn't admitting failure. It's the natural progression of a team that's learned something.

Build. Ship. Repeat. And sometimes, when the code has fought you long enough, burn it down and build again — smarter this time.

All Articles

Related Articles

Debt Isn't a Four-Letter Word: How Smart Teams Budget for Technical Shortcuts

Debt Isn't a Four-Letter Word: How Smart Teams Budget for Technical Shortcuts

Done Is Better Than Perfect: Why Shipping Messy Code Is Actually Good Engineering

Done Is Better Than Perfect: Why Shipping Messy Code Is Actually Good Engineering

Ship It and Watch It: How to Build Feedback Loops That Actually Keep Up With You

Ship It and Watch It: How to Build Feedback Loops That Actually Keep Up With You