A founder reaches out. The message is usually some version of this: "Our codebase is a mess. Previous developers left, the thing barely works, we've been patching it for two years. We think we need to rewrite everything. Can you help?"
My first question is never "when do you want to start?" It's: "What makes you think it needs a rewrite?"
The answer is almost always some combination of: the code is hard to read, adding features takes too long, the old developers wrote it in a way nobody understands anymore, and something broke last week that took three days to fix.
None of those are reasons to rewrite. They're symptoms of a system under strain — and the question of whether rewrite is the right response to that strain is one of the most consequential technical decisions a company can make. It's also one that gets made badly, consistently, because it gets made on emotion rather than analysis.
I've been on both sides of this decision. I've stabilised systems that looked unsalvageable. I've recommended rewrites when stabilisation genuinely wasn't the right answer. The framework I use now is the result of getting both calls right and wrong enough times to know what actually matters.
Why the Rewrite Instinct Is a Trap
The pull toward rewriting is understandable. You're looking at code that's painful to work in. You know roughly what the system should do. You know the current implementation is wrong in dozens of ways. Starting fresh seems like it would clear all of that — a clean slate, no accumulated technical debt, no inherited decisions you wouldn't have made yourself.
This is the "second system" problem, documented by Fred Brooks in 1975 and rediscovered by every generation of developers since. The rewrite starts optimistically. It proceeds reasonably. And then, somewhere around the 60% mark, it runs into the same wall every rewrite runs into: the old system contained knowledge that wasn't in any document, specification, or README. It was encoded in the code itself — in the edge cases it handled, the validation rules it enforced, the integration behaviours that were discovered through production incidents and silently fixed.
The rewrite doesn't have this knowledge. It has to rediscover it. Through incidents. With real users.
Meanwhile, the business is running on the old system. Two timelines now exist: the old system, which still needs maintenance and still breaks, and the new system, which is consuming engineering capacity without producing any business value yet. This lasts longer than anyone estimated — typically 1.5x to 3x longer. Features that were planned for the new system get postponed. The old system gets more fragile because the people who understood it are now working on the replacement.
The most common outcome: the rewrite ships late, is missing features the old system had, and within two years is described by the next team as "the old codebase" — a mess that needs replacing.
I'm not opposed to rewrites. I'm opposed to reflexive ones.
What Stabilisation Actually Involves
Stabilisation is not "leave everything as-is and suffer." It's a deliberate programme of making a system safe to work in — without rebuilding it from scratch.
In practice, it has four components:
Critical path tests. The first thing I do in any rescue engagement is write tests for the paths that matter most: the payment flow, the authentication sequence, the order pipeline, whatever is core to the business working. Not unit tests — end-to-end characterisation tests that capture what the system currently does, good and bad. These are your safety net before you touch anything. I've written about this in more detail in agency codebase takeover — the first 48 hours before you change a line.
Observability. A system you can't see is a system you can't safely change. The second priority is getting logs, error tracking, and alerting into the production environment. Not because it fixes anything immediately, but because it means the next time something breaks, you find out first. Before this is in place, every change is a blind operation.
Selective stabilisation of the highest-risk areas. Not everything needs to be fixed. The goal is to identify the three or four things that are genuinely dangerous — money paths with no idempotency, authentication with gaps, database queries that will fail under moderate load — and fix those first. Everything else goes in a prioritised backlog. Trying to fix everything at once is how stabilisation projects turn into de-facto rewrites with none of the planning.
Strangler Fig for the worst modules. Where a specific module is genuinely unsalvageable — the logic is incomprehensible, the structure is wrong, there's no safe path to incremental improvement — the answer is not to rewrite the whole system, but to replace that module incrementally. You build the replacement alongside the old code, route traffic to it gradually, and delete the original once you're confident. This is the Strangler Fig pattern, and it's the right answer for the 20% of cases where a module really does need replacing. It is not the answer for the 80% of cases where a module is merely unpleasant.
slug="rescue-projects"
text="Inherited a codebase someone's told you to rewrite? I do structured rescue assessments — codebase audit, honest written recommendation, and a path that doesn't require betting six months of engineering on a big-bang rewrite."
/>
When Rewrite Is Actually Justified
There are real cases where rewrite is the right answer. They are rarer than the instinct suggests, but they exist.
The technology stack is genuinely dead. Not "old" — dead. A system built on a framework with no maintained dependencies, no security patches, and no engineers who still know it is a system that cannot be safely operated. The cost of keeping it running — the constant workarounds, the inability to hire, the security exposure — exceeds the cost of replacing it. This is different from "it's not the latest version" or "we'd write it differently today." Those aren't rewrite justifications.
The data model is fundamentally wrong for what the business does now. This is the most legitimate technical rewrite trigger I encounter. If the business has changed direction significantly and the data model no longer reflects how the product actually works, stabilisation becomes increasingly expensive with each new feature. You're not paying down technical debt — you're fighting the data model. This is rare, but when the schema is architecturally incompatible with the current product direction, that's real.
The business has changed what it is, not just what it does. Sometimes a company acquires another product, pivots from B2C to B2B, or decides to embed a formerly standalone system into a larger platform. The old system wasn't designed for this context and cannot be made to fit it through incremental work. This is a legitimate rewrite trigger — but note that it's a business reason, not a code quality reason.
The accumulated risk is higher than the replacement cost. This is the hardest one to measure but it's real. Some systems have accumulated enough contradictions — security exposures, data integrity issues, operational fragility — that the cost of maintaining them is genuinely higher than the cost of replacing them. To make this argument honestly, you need numbers: the cost of the incidents the system has caused, the ongoing engineering overhead, the opportunity cost of features you can't ship. Without numbers, it's an aesthetic argument dressed up as an economic one.
What's notably absent from this list: "the code is hard to read," "developers don't like working in it," "it wasn't built the way I would have built it," and "we inherited it from a team we don't trust." These are not rewrite justifications. They're reasons to improve documentation, run a code review, and write a coding-standards doc.
The Decision Framework
When I enter a rescue engagement and need to form a recommendation, I go through a set of questions in roughly this order.
Frequency and severity of incidents. How often does the system fail in ways that affect users? How long does it take to diagnose and fix those failures? A system with one production incident per month that takes four hours to resolve is in a different category than one with daily incidents that require a specialist. The incident log is often the most honest signal about how the system is actually behaving — more honest than the code, which can look terrible and still be stable.
Test coverage on critical paths. Not overall coverage percentage — that number is almost meaningless. Are the core business flows — payment, authentication, the primary user journey — covered by tests that would catch a regression? If yes, you have a foundation to build on. If no, your first job is to create that foundation, not to rewrite.
Age of the stack and dependency health. Is the stack still maintained? Are there known security vulnerabilities in dependencies with no upgrade path? A system on a deprecated Node.js LTS with unpatched CVEs in production dependencies is a different situation than a system on a current stack with messy application code.
Team turnover and knowledge concentration. Who understands how the system works? If one engineer left six months ago and took all operational knowledge with them, you don't necessarily have a rewrite problem — you have a knowledge recovery problem. Rewriting won't give you the knowledge back; it'll just give you a new system nobody fully understands.
Data model coherence. Does the database schema reflect what the product actually does? Or has it accumulated tables, columns, and relations that map to a version of the product that no longer exists? Schema drift — the gap between what the migrations say and what the product needs — is one of the more reliable indicators of how much architectural work stabilisation will involve.
Business horizon for feature delivery. How long can the business wait for new feature delivery while engineering is consumed by rescue work? A company with runway pressure and stakeholders expecting visible progress in three months is in a different position from one with time to invest in a six-month stabilisation. This doesn't change what the right technical answer is, but it changes what plan is actually feasible.
Cost of the incidents versus cost of the rewrite. This is where the analysis has to get honest. If incidents are costing the business €5,000 per month in support time, lost revenue, and engineering hours, and stabilisation would cost €40,000 and take the incident rate to near zero, stabilisation pays for itself in eight months. If stabilisation would cost €40,000 and reduce incidents by 30%, the math looks different. Actual numbers, not estimates, not feelings.
What I Usually Find
In the rescue projects I've taken on, the breakdown has been roughly this: around two-thirds of the time, the right answer is stabilisation — often with Strangler Fig replacement of one or two modules. One third of the time, there's a case for some form of rewrite, and in most of those cases, that rewrite is partial rather than complete: replace the payment module, keep the order management; rebuild the auth layer, keep the rest.
Full big-bang rewrites — throw everything out and start from scratch — have been the right answer in a small minority of cases. They've been cases where the technology was genuinely unsupported, or where the data model was so wrong that every feature was a fight against the schema, or where the system had accumulated security debt severe enough that the operational risk of running it exceeded the disruption of replacement.
The pattern I see most often is a codebase that's genuinely difficult to work in — unclear structure, missing tests, accumulated inconsistencies — but that still has sound core logic and a data model that reflects the product correctly. That system does not need a rewrite. It needs someone to read it carefully, understand what's load-bearing, and build a stabilisation plan that makes it safe to change without touching everything at once. That work is less dramatic than a rewrite and produces a working system faster.
Before I give any recommendation at all, I spend two hours reading the code, which I've described in detail in technical due diligence before a rewrite. The recommendation only comes after that. Anyone who tells you whether to rewrite or stabilise before they've read the code is guessing.
The Question Worth Asking First
There's one question I've learned to ask before any technical analysis, and the answer changes how everything else is framed.
"If the codebase were clean and well-tested tomorrow morning, would you know what to build next?"
If the answer is yes — you have a clear product direction, defined features, a team ready to execute — then the technical question matters a lot. Getting the codebase to a state where it can support that work is worth investing in.
If the answer is no — if the real problem is unclear direction, team misalignment, or business decisions that haven't been made — then the codebase is not the primary problem. Fixing it won't fix what's actually broken. I've seen rewrites happen for this reason: the technical decision becomes a proxy for a non-technical crisis, and the rewrite provides the feeling of action while the real problem waits.
The technical decision is consequential. It's also sometimes the second decision, not the first.
If you're at this decision point — a codebase that's struggling and pressure from multiple directions about what to do — the due diligence post describes exactly what I examine before forming any recommendation. The agency codebase takeover post covers the first days of a stabilisation engagement in detail. And if the codebase was built mostly by AI, vibe-coded codebase patterns describes the specific failure modes that look like chaos but follow a consistent pattern.
Top comments (0)