"Let's just rewrite it" — the most expensive sentence in an IT project
Every team that has worked on the same codebase for more than a year eventually says it. Usually at a retrospective, usually after the third release in a row that broke something. The sentence sounds reasonable: the code is bad, so let's write good code. The problem is that this is not a technical decision — it is a financial decision to freeze product development for several quarters, and it is usually made on emotion rather than numbers.
In audits this question comes up almost every time: keep developing or rewrite. In our article on what a source code audit covers, we wrote that a rewrite is almost always the worst answer, and that if we do recommend one, we have to defend it with numbers. This article shows those numbers and the specific decision criteria.
Why a rewrite is so tempting
It is worth naming the mechanism, because as long as it stays unnamed, it wins every argument.
Reading code is harder than writing it. This is the oldest observation in the industry. Someone else's code always looks worse than your own, because you cannot see the intent in it — only the result. A developer reading a convoluted function does not see the three production incidents that made it that way. They see a mess.
A new project has no problems yet. Greenfield is the state in which every decision still lies ahead, so none of them has turned out wrong yet. It is an illusion of comfort that fades by month three — the new system starts accumulating its own compromises at exactly the same pace as the old one.
A rewrite is easier to plan than a refactor. A new system can be drawn on a slide and split into phases. Gradually fixing an existing one means hundreds of small decisions spread over months, with no impressive milestone in the middle.
These three things mean that rewrite estimates are systematically too low, while refactor estimates are too high. Before you compare the two paths, assume the rewrite figure is optimistic by at least a factor of two.
The real cost of a rewrite
The cost is not writing the new system. The cost is everything around it.
Maintaining two systems at once. Throughout the rewrite, the old system has to keep running, receive security patches and respond to external requirements — a change in a payment API, a new Android version, a regulatory requirement. Every such change is now done twice. That is typically 20–30% of the team's capacity that was not in the original plan.
Feature freeze and its business cost. For several quarters the product gets no new features, because the team is rebuilding the ones it already has. For the sales team, that means a concrete answer to the customer question "will you add X?" — and the answer is "not this year". This is the item most often left out of the calculation, and usually the largest.
Knowledge buried in odd places. That illogical condition in the billing module is usually not a mess — it handles the case of a customer who reported an issue two years ago. The new system recreates features from their description, not from the incident history, so it rediscovers those cases one by one, in production.
The second-system effect. A team that finally gets the chance to do everything "properly" tends to over-engineer. The new system gains layers of abstraction for needs that will never arise. Sometimes it ends up more complicated than the one it replaced.
A practical rule from our projects: the real cost of a rewrite is 2–3× the first estimate, and the time to feature parity with the old system is usually 1.5–2× what was planned. If the decision still favours a rewrite after applying those multipliers — it is probably the right one.
Five questions that actually settle it
1. Where does the problem lie — in the code or in the architecture?
This question settles the most. Ugly code in a sound architecture can be fixed incrementally, module by module, without halting development. Bad architecture — no boundaries between modules, business logic spread across the UI, everything depending on everything — cannot be fixed locally, because every change touches the whole.
A practical test: can you replace one module without touching the rest? If so, refactor. If every change means touching ten files across four layers — the problem is structural, and refactoring alone will not solve it.
2. Is the platform alive?
A framework with no support, a language version past end of life, a library critical to the system abandoned by its author, an SDK the hardware manufacturer no longer releases. If you cannot patch a security vulnerability because there is nothing to update, a debate about code quality is beside the point. This is one of the few arguments that on its own justifies a rewrite.
Watch out for the milder version of this problem: a framework two major versions behind usually calls for a migration, not a rewrite — expensive, but many times cheaper.
3. How much does freezing the product cost?
This one can be quantified. How many features will not get built over the next two to three quarters, and what does that mean for sales, retention or commitments to customers? If the product is still winning its market, a feature freeze can be more dangerous than technical debt. If the product is mature and stable — the cost of a freeze is low, and a rewrite becomes a realistic option.
4. Does the team know the domain?
A system rewritten by people who do not know the business rules means reconstructing those rules from the code — exactly the work the rewrite was supposed to avoid. If the people who built the first version are unavailable, the risk jumps sharply, because there is nobody to ask why a given condition is there.
5. Are the critical paths covered by tests?
Refactoring without a safety net is risky, but tests can be added. Rewriting without tests for the old system is worse: you have no executable specification of what the new system is meant to do, so the only people verifying parity are your users after release.
The third way: the strangler pattern
The choice is rarely binary. The strangler fig pattern — the new system grows around the old one and gradually takes over its functions until the old one is no longer needed — lets you have both: modern code and continuous product development.
In practice it looks like this:
- Put a traffic-routing layer in place. A proxy, a gateway or in-app routing that decides: this request goes to the old system, that one to the new. Without this step, the pattern does not work.
- Pick the first module — small and on the edge. Not the most broken one, and not a central one. The point is to rehearse the mechanism, not to make an impact.
- Move the feature, redirect the traffic, observe. For a while both systems can run in parallel on the same data, with their results compared.
- Repeat, starting with the highest-value modules. After each step the product works, and you can slow down or stop if priorities change.
The biggest advantage is organisational, not technical: at every stage you have a working product and you can stop. A rewrite does not give you that — abandoned halfway, it leaves two incomplete systems and a wasted quarter.
The price is real too: throughout the migration you maintain the routing layer and a dual data integration. It is slower and less elegant than a rewrite — but it is one you can survive.
In mobile projects we apply the same mechanism module by module: new screens are built in the new architecture, while old ones stay until a business change touches them. You can read more about how we approach cleaning up neglected apps in our app reactivation service description.
When a rewrite really is the right decision
There are situations in which a rewrite is the only honest answer:
- A dead platform with no migration path — there is nothing to update and no security patches.
- No source code, or no way to build the project. A surprisingly common case in takeovers: the app is in the store, but there is no repository, or no certificate it was signed with.
- The business model has changed fundamentally. A system designed around one large client can rarely be turned into a multi-tenant SaaS without rebuilding its foundations.
- Scale beyond the design. An architecture built for a thousand users that now serves a hundred thousand does not need a refactor — it needs a different design.
- The system is small. With only a few weeks of work involved, this whole calculation stops mattering — rewrite it and don't call a workshop.
Notice that none of these points says "the code is ugly". Ugly code is an argument for refactoring, never for a rewrite.
How to make this decision responsibly
Three things worth doing before you decide:
Measure instead of arguing. Lead time from idea to production, number of regressions per release, onboarding time for a new developer, share of sprint time spent on fixes. Without these numbers the conversation is an exchange of impressions, and it is won by the most assertive person in the room.
Run a trial on one module. Pick the most problematic part and try to clean it up in two weeks. The outcome of that trial will tell you more than a month of analysis: if it worked — you have your answer and a working module. If you could not touch it without dragging in the whole system — you have hard evidence for rebuilding the architecture.
Ask for an independent assessment. This decision carries strong bias on both sides: the team that wrote the code will defend it; the team taking over will want to rewrite it. A second pair of eyes from outside has no stake in it — which is why in our audits and consultations we treat this question as a separate section of the report, with estimates for both paths.
Separately, it is worth distinguishing technical debt you can live with from debt that genuinely blocks development — we cover the criteria for that split in our article on when to pay down technical debt and when to live with it.
Summary
A rewrite is tempting because it promises a clean slate — and it gets priced as if a clean slate were free. In practice you pay with double maintenance, a product freeze and the rediscovery of edge cases the old system has handled for years.
In most situations the right answer is gradual replacement — less spectacular, but reversible at every stage. Save the rewrite for cases where the old system genuinely has no future: a dead platform, missing source code, a changed business model.
If you are facing this decision and want someone to cost out both paths before the money goes into one of them — get in touch or get a quote. See also our case studies: quite a few of them started with exactly this question.