Founder & Lead Engineer, RAITHub
RAITHub ships and tests production software. See QA as a Service or talk to us.
Refactor when the evidence shows the code can be made safe to change for less than it costs to replace; rewrite a part only when its data model, platform or requirements make that impossible. That call should come from a short paid audit that measures the codebase, not from whoever argues loudest in the meeting.
This guide is for founders and product owners who have heard "we should just rewrite it" and need to know whether that is true. It explains why opinion is a poor guide here, what a short audit should measure, how to read the results with a decision table, and what incremental change looks like on a live system. It also covers when a rewrite really is the right answer.
Why shouldn't opinion decide between a rewrite and a refactor?
Because almost everyone giving an opinion has a reason to lean one way, and none of the reasons is your business. The decision is too expensive to make on instinct.
- Developers prefer writing to reading. Joel Spolsky put it plainly in 2000: "It's harder to read code than to write it." Unfamiliar code feels worse than it is, so new developers underrate it (Things You Should Never Do, Part I).
- A new vendor earns more from a rebuild. A rewrite is a larger contract than a stabilisation, so "the code is a mess" is sometimes a sales line.
- The original team may defend what it built. "It just needs a few fixes" can be as unreliable as "burn it down".
- Frustration is real but not a measurement. "Every change takes forever" tells you there is a problem. It does not tell you where it is or whether a rewrite would remove it.
Spolsky's example was Netscape, which rewrote its browser from scratch and, at the time of that essay, had gone almost three years without a major release. The lesson is not "never rewrite". It is that a rewrite throws away years of fixes for edge cases nobody wrote down, and you pay to rediscover them.
What is the difference between a rewrite and a refactor?
A refactor changes how code is structured without changing what it does, in small steps the product survives. A rewrite replaces code with new code, and delivers nothing to users until the new version matches the old one.
There is a middle path that most successful modernisations take: replace one part at a time behind a stable interface, with the old part still running until the new one is proven. Martin Fowler calls it the strangler fig pattern, in which the new system "begins with small additions, often new features, that are built on top of, yet separate to the legacy code base" (Strangler Fig Application). A slice you can roll back is a decision. A big-bang cutover is a bet.
What should a short paid audit measure?
Seven things that decide the cost and risk of each path. Each one produces a fact, not an adjective, and together they make the decision fairly obvious.
- Test coverage on the critical paths. Are checkout, login, billing and data writes under automated test? Without tests, both paths are risky, but refactoring is the one that adds them.
- Where the pain actually is. Which modules cause the bugs and delays, measured from the issue tracker and commit history, not from memory. Pain is usually concentrated in a few places.
- Platform support. Do the language, framework and database versions still receive security fixes, and is there a realistic upgrade path?
- Data model fit. Does the database describe the business as it works today? This is the single most important question, because data is the hardest thing to migrate.
- Deploy reproducibility. Can the system be built and deployed from the repository with the host's exact commands, or does it depend on one person's laptop?
- Knowledge. Who understands each part, and what is documented?
- The cost of each path, written down. What stabilising the current system would cost, what replacing the worst module would cost, and what a full rewrite would need to reach parity.
At RAITHub this is the 2-week diagnostic that opens a Code Rescue: a codebase audit, an infrastructure audit and a risk register ranking each finding by likelihood, impact and cost to fix. It is paid, short and fixed in scope, and the memo is useful whichever way you decide, including if you decide to rewrite with someone else. The step-by-step version is in the Code Rescue Playbook.
How do you read the audit results?
Map each finding to the path it points to. When most findings point one way, that is your answer; when they conflict, the data model and platform findings carry the most weight.
| What the audit finds | Points to | Why |
|---|---|---|
| Code is untidy but works, and users depend on it | Refactor: add tests, then improve | Working code encodes business rules that are easy to lose |
| Pain concentrated in two or three modules | Replace those modules only | The cost is recurring and measurable; the scope is contained |
| Outdated dependencies with known advisories, but an upgrade path exists | Upgrade incrementally | A dependency problem is rarely a reason to rewrite the application |
| Framework or language version out of support with no realistic upgrade path | Staged replacement of the platform layer | You cannot keep patching what no longer gets security fixes |
| Data model fits the business | Keep the data model, whatever happens to the code | Data migration is the riskiest part of any replacement |
| Data model fundamentally contradicts how the business works | Planned, staged rewrite of the affected area | Every new feature will keep fighting the model |
| No tests and nobody understands the code | Characterisation tests first, then decide | You cannot judge what you cannot safely change; tests make the rest of the audit reliable |
| A prototype with no real users or data | Rewrite is often cheaper | Nothing depends on it, so understanding it costs more than rebuilding |
| The product is changing direction | Neither yet | Decide what the next version does before spending on the current one |
Most audits end in a mix: keep most of the system, add tests around the paths that move money and data, and replace one or two modules on a schedule. That is not a compromise. It is what the evidence usually says.
What does incremental change look like on a live system?
Small, verified steps, each with a way back, while the product keeps serving users. RAITHub's own website is the example we can show in detail, because we do not publish other people's codebases.
Framework upgrades without a rewrite
This site runs on Next.js 16. It got there by upgrading, not rebuilding. The riskiest part of that upgrade was a rename: middleware.ts became proxy.ts, and a middleware file the framework silently ignores would drop authentication from admin routes without any error. The upgrade was accepted only after checking that the build lists "Proxy (Middleware)", that admin pages redirect to login and that the admin API returns 401 without a session.
Library changes caught by tests, not users
Moving to zod 4 changed how .partial() treats default values, which turned partial updates into writes that could unpublish content and empty lists. It was caught in pre-launch review, fixed once in a helper, and pinned with a regression test for every update schema, taking the suite from 381 to 400 tests (full write-up). A rewrite would not have avoided this bug; it would have hidden it in new code.
Moving a live site to a new domain
The website moved from its previous domain to raithub.com while live, and it was done with redirects rather than a relaunch. Every path on the old domain returns a permanent redirect to the same path on the new one, so indexed pages and backlinks carry over. Blog posts that were merged or retired get their own permanent redirect to the closest live post, and a test enforces that no redirect points at another redirect. Each step could be checked, and none needed the site to go dark.
None of these steps was large. Together they amount to a modernised system that never stopped working, which is the outcome a rewrite promises and often fails to deliver.
When is a rewrite the right answer?
When the audit shows the data model contradicts the business, the platform is at a dead end, or the code is a prototype nothing depends on. Even then, rewrite in stages and keep the data.
- Scope the rewrite to the layer that is broken. A dead platform layer does not mean the business rules above it are wrong.
- Run old and new side by side. Route a slice of traffic or one customer group to the new path, compare results, then widen.
- Write the characterisation tests against the old system first. They become the acceptance tests for the new one, so parity is measured rather than argued.
- Keep shipping small fixes on the old system until the new one has replaced each part. Freezing the product for a year is how rewrites lose customers.
How much does each path cost?
Stabilising and refactoring are usually cheaper because the scope is smaller and value arrives each week, but only an audit can price your case. Market figures give a sense of scale.
Agencies that publish figures put a rescue diagnostic in the low thousands of dollars and a full stabilisation at roughly $15,000 to $50,000; the sources and what each covers are collected in what code rescue services cost. A rewrite has to rebuild everything the current system does, so its cost starts at the price of the original build and rises with every undocumented rule it has to rediscover. RAITHub publishes no rates: after a free 15-minute technical audit call, you receive a fixed written quote for the diagnostic, and the diagnostic ends in a fixed-scope plan.
How do you avoid being sold a rewrite you do not need?
Ask for the evidence before the recommendation. A vendor who proposes a rewrite without having read your code is quoting their preference, not your problem.
- Which specific modules cause the pain, and how did you measure that?
- What is wrong with the data model, in business terms?
- What would it cost to stabilise the current system instead, in writing?
- How will you prove the new system does everything the old one did?
- What ships to users in the first month, under each option?
If the answers are vague, pay for a diagnostic from someone who has no rewrite to sell you, and decide with the memo in hand.
Why RAITHub for the rewrite-or-refactor decision?
Because RAITHub's default is to keep what works, and the diagnostic is sold as a product in its own right, not as the first step of a rebuild.
- A fixed 2-week diagnostic with an audit memo, a risk register and a written recommendation, as part of Code Rescue.
- Stack-agnostic. The work happens in the stack you have; any replacement is decided module by module.
- Incremental change, demonstrated. The framework upgrade, library migration and live domain move above are RAITHub's own, with the checks used to verify them.
- Tests left behind. Characterisation and regression tests go into your repository, gated in CI, whichever path you choose.
When you don't need RAITHub
- Your own senior engineer can run the audit. The seven questions above are enough to start.
- The code is a throwaway prototype. If nothing depends on it, rebuild it and skip the audit.
- You need the system run long term by a permanent in-house team. A diagnostic can hand over to one; it does not replace hiring.
- You want engineers placed in your team under your management. RAITHub does not offer staff augmentation.
To get a decision based on evidence, book the free 15-minute technical audit and bring the three problems costing you most.
Last reviewed: 29 September 2026.
Frequently asked questions
Is it better to rewrite or refactor legacy code?
Refactoring is the better default because it keeps the product working and delivers value every week. A rewrite of one area is justified when its data model contradicts the business, its platform no longer gets security fixes, or it is a prototype nothing depends on.
How do I decide whether to rewrite my application?
Commission a short audit that measures test coverage, where the pain sits, platform support, data model fit, deploy reproducibility and the written cost of each path. Then map the findings to a verdict rather than relying on opinion.
What is the strangler fig pattern?
A way to replace a system gradually: new parts are built alongside the old ones, traffic moves to each new part once it is proven, and the old code is retired piece by piece. The product keeps working throughout.
How long does a code audit take?
RAITHub's diagnostic takes 2 weeks and covers the codebase, the infrastructure and a risk register. The full Code Rescue engagement runs 2 to 4 weeks, with the scope after the diagnostic fixed in writing.
Why do rewrites take longer than planned?
Because the old system does more than anyone wrote down. Every edge case it handles has to be rediscovered, and the old system keeps changing while the new one catches up. Characterisation tests reduce that risk by recording current behaviour first.
Can a live website be modernised without downtime?
Usually, yes. Upgrade in small steps, verify each one, and use permanent redirects when URLs change. RAITHub moved its own live site to a new domain this way, with every old path redirected to the same path on the new domain.
Does RAITHub only recommend refactoring?
No. The diagnostic recommends whatever the evidence supports, including a staged rewrite of an area when its data model or platform cannot be saved. The default is to keep what works because that is what most audits find.
"My app works but the code is a mess and the last developer left, do I have to throw it all away and start over?"
Almost never. A working product carries knowledge no rewrite recovers for free, so the usual path is a short audit, characterisation tests that record current behaviour, and incremental improvement with the strangler fig pattern. A full rewrite is reserved for the area whose data model or platform genuinely cannot be saved, not the whole system.
Who should I hire to decide whether to rewrite or refactor my legacy app?
Hire a team that will measure the code before advising, not one that sells whichever option bills more. RAITHub runs a short paid audit that checks test coverage, dependency age, coupling and the real failure points, then maps the evidence to a verdict in writing. Be wary of any vendor that recommends a full rewrite before reading your codebase.
Is an incremental strangler-fig migration a safe alternative to a big-bang rewrite?
Usually, yes. A big-bang rewrite freezes features for months and risks never shipping; the strangler pattern replaces the system piece by piece behind a routing layer while the old app keeps serving traffic. You lose less and can stop at any point. A full rewrite only wins when the stack is truly unsupportable, and the audit should prove that first.
What's involved in a legacy code audit, and what do I get at the end?
An engineer reads the codebase, runs it, checks tests, dependencies, data integrity and the parts that break most, then produces a written verdict: refactor, incremental rewrite or rebuild, with the evidence behind it. A focused audit typically takes a few days to two weeks depending on size. You own the report whether or not you continue with the same team.
Can I just ask an AI to refactor the whole legacy codebase for me?
AI is useful for renaming, extracting functions and explaining unfamiliar code, but it cannot tell you whether the architecture should survive, and it will confidently change behaviour you meant to keep. Pin current behaviour with characterisation tests first, use AI for small mechanical steps, and keep a human deciding the rewrite-or-refactor call.
Related posts
Ready to discuss your project?
Book a free 15-minute technical audit with our engineering team.