Skip to content
[ 05 ]Journal

Rebuild or Refactor? How to Decide on a Stalled Product

Refactor in almost every case, replacing what cannot be saved piece by piece. A rewrite wins only when the platform is dead or the product is small.

Rebuild or Refactor? How to Decide on a Stalled Product
Listen to this · AI voice11 min

By , Founder & CEOPublished

The short answer: refactor, in almost every case, and replace the parts that cannot be saved one at a time while the product keeps running. A full rewrite from scratch is the right call only when the platform itself is dead, the product has changed so much that the old code models the wrong business, or the system is small enough to rebuild in weeks rather than quarters.

Key takeaways
  • A from-scratch rewrite freezes the product you are selling while you rebuild the one you already have. Joel Spolsky called it "the single worst strategic mistake that any software company can make".
  • The middle path, the strangler fig, replaces a system piece by piece behind a routing layer, so the old product keeps earning while the new one grows.
  • Decide on evidence, not on how the code feels to read: change lead time, change failure rate, defect rate, test coverage of the critical paths, and the age of the dependencies.
  • Most stalled products have a process problem before they have a code problem. Tests, a pipeline and a repeatable deploy fix more than a new framework does.
  • Two weeks of structured assessment is enough to make the call, and far cheaper than finding out six months into a rewrite.

The question arrives in one of three forms. A founder has inherited a product from an agency that has stopped answering. A CTO has a codebase where every small change takes a fortnight and breaks something else. Or a new engineering lead has read the code for a week and announced that it all needs to go. Somebody has to decide whether to fix what exists or start again, and the decision is usually made on instinct, by whoever argues loudest. Here is how we would make it instead.

Why is a rewrite riskier than it looks?

Because the old code holds years of decisions nobody wrote down, and a rewrite has to rediscover all of them while the business waits. Spolsky's essay on Netscape, written in 2000 and still the clearest statement of the problem, points out that the company shipped no major release for almost three years while it rebuilt its browser, and watched its market share fall the whole time. His diagnosis is blunt: when you throw away code, you throw away "all those collected bug fixes", and you hand your competitors "a gift of two or three years".

He also explains why engineers keep proposing it. "It's harder to read code than to write it." Code someone else wrote always looks worse than code you are about to write, because you cannot see the edge cases it is quietly handling.

Martin Fowler makes the same point from the modernisation side. The plan to "just build a new system that does exactly the same" sounds simple, he writes, but he and his colleagues have "seen this simple-sounding plan go down in flames most of the time". Users cannot wait for new features during the rebuild, the existing behaviour is harder to specify than it looks, and much of it is behaviour nobody actually wants any more.

None of this means rewrites never work. It means the cost is not the build. It is the months in which you maintain two products, ship features to neither, and learn what the old one was doing only when a customer reports that the new one does not.

When does refactoring win?

When the product works and the problem is that it is slow and frightening to change. That describes most stalled products.

Fowler's definition is precise: refactoring is a change to the internal structure of software "to make it easier to understand and cheaper to modify without changing its observable behavior". The customer sees nothing. The team sees the next feature take four days instead of six. Fowler frames that two-day difference as the interest on technical debt, and the argument for refactoring is simply that you stop paying it, area by area, starting where the team spends the most time.

Refactoring is usually the right call when:

  • The product has paying users and the core workflow does what they need.
  • The language and framework are still supported, even if they are not fashionable.
  • The pain is concentrated. Most of the trouble lives in a handful of modules, which is the usual shape.
  • The data model is roughly right. The tables describe the business you are actually in.
  • The real gaps are around the code, not in it: no tests, no continuous integration, a deploy that one person knows how to run.

That last point matters more than any other. A team that cannot change the code safely will find every codebase unmaintainable, including the new one. Putting tests, a pipeline and a repeatable deploy around what exists is the first job either way, and it often makes the rewrite question go away.

When does a rewrite win?

When the foundation itself is wrong, not just untidy. The honest list is short:

  • The platform is dead. The language version, framework or hosting is out of support, has known security holes that cannot be patched, or cannot hire anyone to work on it.
  • The data model describes a different business. The product started as one thing and became another, and every new feature fights the schema.
  • It is a prototype that was never meant to carry real users. A demo built in a weekend is not legacy code; it is a sketch, and treating it as a foundation is the expensive option.
  • It is small. If the whole system can be rebuilt in a few weeks by people who understand what it does, the Netscape risk largely disappears.
  • Requests cannot be intercepted. If there is no way to route some traffic to new code while the rest goes to the old, an incremental replacement is not possible.

Even then, "rewrite" rarely needs to mean "stop everything and start again".

What is the strangler fig pattern, and when should you use it?

It is a way to replace a system gradually instead of all at once, and it is the default answer whenever a rewrite is justified for a product people are already using. Fowler named it after the strangler figs he saw in Queensland, vines that grow around a host tree until they can stand on their own.

Microsoft's architecture guidance describes the mechanics plainly. You put a façade, a proxy, in front of the old system. At first it routes almost every request to the legacy code. Each time a piece of functionality is rebuilt, the façade sends that slice of traffic to the new code instead, until the old system has nothing left to do and can be switched off.

The advantage is not speed. Fowler is clear that the approach "doesn't make the exercise easy". What it does is make both the investment and the returns "occur gradually and visibly". The business keeps shipping, each replaced slice is tested against real traffic, and you can stop at any point with a working product.

Microsoft's guide also lists when the pattern does not fit: when requests to the old system cannot be intercepted, or when you have no access to its source code and no other integration point, such as its database. For a typical web product built in the last decade, neither applies.

Which signals should you measure before deciding?

Five numbers, gathered over the last three to six months, will tell you more than any opinion of the code.

  1. Change lead time. DORA, the research programme behind the most widely used software delivery metrics, defines it as the time from a change being committed to it running in production. If a one-line fix takes two weeks to reach users, find out where the two weeks go before blaming the code.
  2. Change failure rate. DORA's measure of how often a deployment needs immediate intervention, a rollback or a hotfix. A high rate with no tests is a testing problem. A high rate with good tests is a design problem.
  3. Defect rate by area. Count bugs per module, not in total. If most of them come from a small part of the system, you are looking at a targeted rebuild of that part, not a rewrite.
  4. Test coverage of the critical paths. Not a single percentage. Fowler's view is that coverage is "a useful tool for finding untested parts of a codebase" and "of little use as a numeric statement of how good your tests are". What you want to know is whether sign-up, payment and the core workflow are covered.
  5. Dependency age. How many major versions behind are the framework, the language runtime and the main libraries, and are any of them out of security support? This is the number most likely to turn a refactor into a forced migration.

Two more are worth a conversation rather than a metric: how long it takes a new engineer to ship their first change, and whether anyone on the team can explain how a release actually reaches production.

What does a two-week assessment look like?

Two weeks is enough to replace an argument with a document. A plan we would follow on any codebase:

Days 1 to 2: access and inventory. Get the repositories, the hosting accounts, the database and the third-party keys into your own organisation. List every service, dependency and integration, with versions.

Days 3 to 5: run it. Build the system from scratch on a clean machine, deploy it to a fresh environment, and walk through every core workflow with real-shaped data. Whatever cannot be done from the documentation is a finding.

Days 6 to 7: measure. Pull lead time, change failure rate and defects by module from the version history, the deploy log and the issue tracker. Run a coverage report and a dependency audit.

Days 8 to 9: map the pain. Mark each module as keep, refactor or replace, with one sentence of evidence for each. Look hard at the data model, because that is the part that is most expensive to get wrong twice.

Day 10: write it down. One document: what works, what does not, the recommendation, the order of work, the cost and the date. If the answer is a staged replacement, name the first slice and where the routing layer will sit.

The point of the document is that the decision can be checked. Someone who disagrees can argue with the evidence instead of with a feeling.

How does AW3 approach it?

We start every project rescue with a short, fixed-price audit: we read the code, run it end to end, and write down what works, what does not, and what it will take to finish, with a price. You get that document whether or not you hire us for the rest.

We do not throw existing code away by default. The audit decides what stays: code that works and has a future is kept, and code that cannot be made reliable is replaced. Before adding anything, we put tests, continuous integration and a repeatable deployment around what exists. Most takeovers keep more than they rebuild, and the reasoning is in writing before any of it happens.

If you are choosing a team to do this work, our twelve questions to ask a software development agency apply here too, especially the one about whether they have taken over someone else's project. And if the assessment points to a genuine rebuild of a small product, the ranges in how much an MVP costs in 2026 are a fair guide to what the new first version should cost.

Not sure whether yours is a rescue or a rebuild? Send us what you have and we will tell you which.

Sources