Software Rewrites: Why They Fail and What to Do Instead

By Jeffrey Sherman and Isaac Askew ·

Most software rewrites fail. Not in a dramatic, instructive way: they fail slowly, over months, until someone finally cancels the project and the original system is still in production. Here is why that keeps happening, how to tell when it is happening to you, and what to do instead.

Drawn from The Rewrite Trap, the book by Jeffrey Sherman and Isaac Askew, and from six years of the Never Rewrite podcast.

What a software rewrite actually is

Software has a vocabulary problem. Refactor, rebuild, rewrite, and modernize get used interchangeably, and the imprecision is expensive: it lets a small, safe change and a company-threatening bet share a word. The distinctions:

The rewrite trap: wasting time and resources starting over from scratch to avoid understanding the system you are already working in.

Why rewrites are so tempting

Starting over is one of the most seductive ideas in software. A legacy system can be a nightmare to work in. A simple feature takes weeks. You are too scared to change anything because there is no test coverage, and you cannot add test coverage because the code is structured so badly that testing it requires an absurd amount of mocking and bootstrapping. The original authors left years ago.

Part of what makes the trap so effective is how good it feels on the way in. A rewrite pushes delivery to the very end, which buys months of smooth sailing first. The team designs the architecture, builds the frameworks, writes clean code, and for a while everyone gets exactly what they hoped for: relief from the old grind, modern tools, real tests, work they enjoy.

The reckoning comes at delivery. Putting the system in front of real production traffic exposes the missing features, the unhandled edge cases, and the new bugs all at once.

What people think they are getting

Rewrites get approved because every group looks at the same old system and reaches the same conclusion. What "better" means depends on where you sit, and each group's version of better is a promise the rewrite is unlikely to keep.

GroupWhat they think they are gettingWhat actually happens
Developers A modern codebase, better stack, fewer bugs, faster development, better culture If the team could produce a clean stack with good coverage under this leadership, it already would have. Starting over does not touch whatever is stopping them.
Engineering leaders A strategic refresh, better retention, easier hiring, lower long-term costs The new system takes years. Bugs in the old one persist, deadlines slip, the team is pulled into supporting both.
Product "Feature delivery speeds up once the rewrite is done" Feature development halts. The backlog grows. Customers wait.
Finance Better reliability, lower operational cost, long-term ROI One of the most expensive bets a company can make. Costs spiral, revenue may drop during transition, ROI may never arrive.
Customers Bugs fixed, features sooner, better experience Most rewrites are invisible to customers until velocity slows and bugs multiply, and then they notice without knowing why.

There is nothing objectionable on that list. It would be great if everyone got everything. Part of the rewrite trap is focusing on the benefits and ignoring the tradeoffs and the risk.

The six ways rewrites actually end

Each of the six has its own flavor of disaster. What they share is the ending: the original system is still in production and your people still hate working in it.

1. The Never-Ending Rewrite

Six months drifts into two years as developers discover functionality they did not know existed. Feature parity stays just out of reach. Budgets explode, morale tanks, and years later the project is canceled or quietly defunded. This one is worst for personnel. Turnover is extremely likely, and the exodus of product people takes the institutional knowledge with it. Paradoxically that makes the rewrite more appealing to whoever arrives next, because the unknowns in the original system have grown.

2. The Half-Launched Rewrite

A small part of the new system reaches production, but there is no compelling use case for it alone. Other teams will not migrate for the sake of migrating. The company maintains both systems forever: bugs from the old, bugs from the new, bugs from the migration, plus a whole new class of bugs from the two behaving slightly differently.

3. The Over-Engineered Rewrite

Given a blank canvas, developers build their dream architecture: elegant, general, a framework for building solutions instead of a solution. It abstracts away every problem except the one the business has, then collapses under its own complexity before reaching production. It does at least fail fast, which limits the waste.

4. The Business-Alienated Rewrite

By the time it ships, the company has outgrown the system it was built to replace. Deliver the better version of today's software two years from now and it arrives out of date. You end up asymptotically approaching a target you never quite reach.

5. The Product-Led Rewrite

Product gets excited about new features and designs. Functionality is fundamentally similar but different enough that everything must be rewritten, and everyone underestimates the work of recreating the boring parts. The new system launches without core features, so the old one stays on. Two systems: the one you want customers to use and the one they need to use.

6. The Vasa Rewrite

In 1628 Sweden launched the Vasa, the most powerful warship it had ever built. The king wanted it larger and more heavily armed than designed, so extra guns and an extra deck were piled on. A stability test before launch alarmed the men running it, but the date would not move. The Vasa caught its first strong gust and sank barely thirteen hundred meters into its maiden voyage.

The Vasa Rewrite makes it into production. The team deploys and replaces the old system in a single cutover, and for a moment it looks like the rare success. Then real users arrive and every broken workflow surfaces at once, publicly. The others waste time and money; this one also destroys trust. This is the outcome that costs executives their jobs.

A $100 million example: the Sonos app

Around 2022, with its mobile app aging and increasingly hard to maintain, Sonos committed to rewriting it from scratch. The reasoning was defensible: customers wanted better performance and home integration, the codebase was slow to work in, and executives wanted to move faster than the old foundation allowed. The project — codenamed "Passport" — was coupled to the launch of the company's first headphones, and that hardware deadline left the software no room to slip.

The app shipped on 7 May 2024. Sonos had told customers the redesign would keep everything they already had. Within hours, owners were reporting the opposite. The update broke alarms, sleep timers, and local library search. Grouped rooms fell out of sync and lost independent volume control. Some older speakers became effectively unusable. Accessibility features the company had been known for stopped working.

By summer, Sonos tied roughly $100 million of its fiscal 2024 revenue directly to the release, and committed $20–30 million more to fixing the app and winning customers back. In January 2025 the CEO stepped down; the chief product officer role was eliminated the same day. Across the episode the stock gave up around a quarter of its value.

Judged purely as software, most of the app's long-term goals were sound. What failed was the delivery model: Sonos pulled away behaviors people relied on with no warning, no fallback, and no path between the old experience and the new one. A slower path was available — ship new capabilities beside the existing experience, use feature flags and progressive rollouts, keep the core workflows alive while their replacements are proven. Instead, trust built over years broke in a single afternoon.

Five signs your rewrite is flailing

No rewrite fails all at once. As Fred Brooks put it, projects get late one day at a time. The earlier you recognize the signs, the more options you have.

  1. The original deadline approaches and the rewrite is not complete. Two inescapable boolean questions: can customers use the new system, and does it have all the features of the old one? If either answer is no, the rewrite is late.
  2. There is no migration plan. Building the new system is not the end. You still have to migrate data, migrate users, and turn off the old system. At worst the plan is Underpants Gnomes: Phase 1, rewrite the system. Phase 2, ? Phase 3, new system in production.
  3. New feature development has restarted on the old system. The freeze is thawing. Product is sneaking in "critical" features, engineers are being pulled back to support old workflows, and the rewrite now chases a moving target with fewer resources.
  4. You start micromanaging. You do not have time for projects that are going well, so inviting yourself to the weekly status meeting measures your confidence, not the team's progress. If the team has to start holding one so you can attend, that is worse. Work reordering that ends up in a spreadsheet outside the regular tracker is a sure sign things are going badly.
  5. Team morale drops. Rewriting a system means rewriting all of it, including the parts that were fine and the parts nobody uses. Teams that were thrilled to escape a hated reporting framework discover they must now migrate all hundred-odd reports built on it.

Do you need to modernize at all?

Before agreeing to rewrite anything, separate two very different statements:

If the second is not true, stop. "This feels gross" is not a business case.

Then diagnose before you prescribe. "We have scaling problems" can mean a performance bottleneck, a fragile script, a manual process that cannot keep up with headcount, or a design constraint that made sense early and hurts now. Ask the team:

If the team cannot answer clearly, there is not enough information to make a decision. A great deal of what gets called a scaling problem turns out to be manual work, and if a process has 200 steps and a small change eliminates 180 of them, that may be enough for a long time.

Signals that more aggressive modernization is earned: you cannot meet customer demand without unacceptable reliability risk; operating cost is rising faster than revenue; incident frequency is increasing with systemic root causes; delivery speed is constrained by architecture; hiring and retention are taking real hits; or the platform is approaching a forcing function like end-of-life or a compliance deadline.

The alternative: TheseusShipping

A full rewrite builds a second ship. TheseusShipping keeps one ship in the water. You are not building a system to escape to; you are improving the one you have until it becomes the one you need.

Distilled, it is three steps:

  1. Notice friction. A bug, code that is hard to understand, code that has not evolved with the business, code that is hard to change.
  2. Reduce the friction. Make it better, not architecturally perfect. The problem does not have to be solved for all time.
  3. Put the change in production.

That is it. The power is slow but compounding. One small change will not restore trust on day one; consistently reducing friction week after week will. The magic is in showing up and showing progress. So is the difficulty.

What this buys you, compared to a rewrite:

What teams underestimate is the price. A rewrite asks for the two things budgets are built to give: money and time. TheseusShipping asks for a little of both plus one thing much harder to grant, a change in culture. A team trained for years to ship features as fast as possible will not switch to deliberate, friction-reducing work because a budget line said so.

The three capabilities that make it work

Noticing friction and reducing it is the easy half. Doing that to a live system without breaking it takes three things, and teams that modernize successfully have all three. They make the parts separable, they make change verifiable, and they control when users meet the result.

Boundaries: make the parts replaceable

Legacy systems get hard to work in when they become entangled. Business rules bleed into controllers, queries hide inside rendering logic, shared utilities quietly wire together components that were never meant to depend on each other. Before you extract or replace anything, put an interface around it: an internal API or a service boundary with a defined contract. This is the step teams skip most often; they want to jump straight to replacement. You cannot replace what you have not first isolated.

This does not mean microservices. Modularization inside the monolith is usually enough, and often trades away less.

Safety: change with confidence

Before you replace code, you have to know what it does. Write characterization tests, which capture how the system behaves today, quirks and edge cases included. They describe what the code does, not what it should do. For business-critical logic, go further and run old and new side by side on the same inputs, comparing outputs and investigating every difference.

Be honest about how far that gets you. When Jeffrey merged two clearing systems at Island ECN, he had the easiest possible case: perfectly deterministic, fixed inputs, no stakeholders, nothing allowed to change. The systems still disagreed by one or two trades out of tens of millions, and getting to an exact match meant fixing every bug in the rewrite and the handful he found in the original. Running side by side does not solve the Two Clock Problem; it only proves you have one.

Controlled replacement: decide when users meet the new code

On 1 August 2012, Knight Capital lost $440 million in 45 minutes because of a botched release. Neither version of the software was broken. What killed the firm was losing control of which version was running, at roughly $160,000 per second.

Hold that next to a big-bang rewrite, which is the largest and least reversible release a team can attempt. The tools that make hundreds of small releases boring are feature flags (deploy code without switching it on; enable for internal users, then a percentage of traffic; roll back instantly) and the strangler-fig pattern (route specific features to the new implementation while the legacy system handles everything else, until what remains is small enough to delete).

Khan Academy did exactly this moving off Python 2.7 as App Engine support approached end-of-life. When they believed a field was ready, the strangler made two calls — first to Python, then to the new Go service — returned the Python value, and asynchronously compared and logged disagreements. That let them verify 100% of production requests for weeks either side of each switch. The migration began in late 2019 and stayed a primary engineering focus for years, but the work shipped continuously and users never felt a cutover.

How to start

A rewrite answers "where do we begin?" for you: start over at line one. Iterative work does not, and the obvious answers are all wrong. "Everywhere" is the same as nowhere. "Whatever feels exciting" puts the team's interest ahead of the system's. "Whatever's most broken" confuses urgency with importance.

You want leverage: a target that is urgent, important, and small. Pick a first replacement that is low risk (failure will not bring down the system), high visibility (users should notice if it works), well understood, and painful enough that the current version is clearly causing trouble. Good candidates: reporting modules, individual UI screens, load-time problems, email or notification systems, permissions logic, ingest pipelines, or anything already behind an API. Avoid core billing flows and fragile infrastructure. They matter more, which is exactly why they make bad experiments.

Then run the loop:

  1. Isolate the boundary. Wrap the target behind a clean API. If you cannot isolate it, you are not ready to replace it.
  2. Capture behavior in tests. Enough characterization tests to change it without flying blind.
  3. Replace behind a flag. Build the cleaner version matching behavior, not internals.
  4. Run in parallel. Feed old and new the same inputs and compare until they agree.
  5. Flip gradually. Internal users, then a slice of traffic, then everyone, with a fast rollback.
  6. Tear out the old code. Delete the legacy implementation; it lives in source control if you need it. Remove the toggles and scaffolding. Update the docs.
  7. Celebrate in public. Progress that nobody sees looks identical to no progress.

If you have already started

Calling off a rewrite is leadership, not failure. Most rewrites fail slowly until they suddenly fail completely, and pivoting early is what saves the team, the business, and the trust. The short version of the playbook:

  1. Get the team leads to tell the truth early. Ask from a learning perspective: here is where we expected to be, here is where we are, what does that tell us? Make it clear that being wrong is fine. Being surprised is not.
  2. Quantify what is working. A failed rewrite still produces reusable components, better tests, cleaner abstractions, and above all a much sharper understanding of the legacy system. Capture that while the people who earned it are still around; it is the one thing you cannot rebuild later.
  3. Define a new path and name it. "We are changing strategies" lands far softer than "we are abandoning this."
  4. Control the narrative. People will tell themselves a story about what happened. Shape it, or it gets shaped for you.
  5. Protect the team. When rewrites stall, people go looking for someone to blame.

To bring salvaged work back into the original codebase, use WAM: wrap it in a compatibility layer, adapt it to honor the existing contract even where that means accommodating ugly reality, and merge it behind a feature flag.

Does AI change the math?

AI makes rewrites faster to start and faster to fail. It does not make them more likely to succeed.

What AI made cheaper is understanding: scouring commits, documents, and old decisions to explain why a system works the way it does, and writing characterization tests to lock that behavior in. That lowers the cost of the incremental path far more than it lowers the cost of the rewrite.

What it does not touch is the organizational risk. An AI-assisted team with vague requirements produces vague systems faster. Integration risk stays exactly where it was. Systems fail because assumptions diverge across boundaries, and AI executes inside whatever context it is handed. Left unchecked, AI will happily propose abstractions and extra features, which is the second-system effect Fred Brooks described in 1975 arriving on a shorter timescale.

And whatever speed AI gives a rewrite, it gives to iteration too. The opportunity cost of a rewrite is still measured in the features you are not shipping while it runs, and when features can ship in days, every day spent rewriting is a day of visible change the customer did not get.

The bottom line

A rewrite promises clarity, control, and a clean finish. What it usually delivers is a long stretch of uncertainty, a widening gap between the plan and what can ship, and trust that erodes while stakeholders wait. The trap works precisely because the early months feel good. By the time the trouble is undeniable, too much is sunk to back out without pain.

Anyone can stop the world and rebuild. Improving the engine while the car is moving takes real skill.

Common questions

Why do most software rewrites fail?

Because the rewrite defers all delivery to a single cutover while the business keeps moving. The old system still needs bug fixes and features, competitors keep shipping, and by the time the new system reaches feature parity the definition of parity has changed. The failure is organizational more often than technical — teams underestimate how hard it is to fully understand a system running in production.

How long does a software rewrite actually take?

Reliably longer than estimated. The six-month estimate becoming a two-year project is the norm, not the exception. In forty years of combined experience the authors have never seen a rewrite start with a serious estimation effort, and never seen one finish on time.

Is a rewrite ever the right call?

Sometimes — but it has to be earned. The test is whether the system is actively preventing the business from operating or growing, not whether it is unpleasant to work in. Even then, the right answer is usually the narrowest modernization that relieves the constraint, not a full replacement.

What is the alternative to a software rewrite?

Incremental, in-place modernization — TheseusShipping. You keep one system in production and replace its parts over time using boundaries (wrap the target behind an API), safety (characterization tests and parallel runs), and controlled replacement (feature flags and the strangler-fig pattern).

Does AI make rewrites viable now?

No. AI makes code generation cheap, but the risk in a rewrite is organizational, not typing speed. What AI genuinely changes is the cost of understanding and testing legacy code — which tilts the comparison further toward improving what you already have.

How do I know if my rewrite is failing?

Five signals: the original deadline is approaching and customers still cannot use the new system; there is no migration plan; feature development has restarted on the old system; you have started micromanaging; and team morale is dropping.

Episodes on this

From the Never Rewrite podcast archive.

Keep reading

Browse the archive by topic