Almost every team eventually faces the same fork: the codebase has accumulated enough friction that something has to change. The question is whether to refactor incrementally — improving the existing system in place — or to rewrite, building a replacement that eventually takes over.
Most teams pick wrong. They rewrite when they should have refactored, or they refactor when the underlying model is unsalvageable. After eight inherited engagements, we use a small framework that gets the decision right more often than instinct does.
The default answer is refactor
Refactoring is almost always the right starting position. The industry's most famous warning about rewrites — Joel Spolsky's "Things You Should Never Do, Part I" from 2000 — is older than most of the engineers reading this, and it has aged unusually well. The reason is that rewrites discard one asset that is almost impossible to recreate: the years of bug fixes, edge cases, and customer-specific behavior encoded in the existing system.
A rewrite optimistic estimate is six months. The honest median is two years, with a feature freeze on the existing product for at least one of those years. During that freeze, your competitors keep shipping.
The cost of a rewrite is not the engineering hours. It is the twelve-to-twenty-four months of feature freeze you pay for in lost market position.
The five questions
Before deciding, answer these five questions honestly. They are ordered from least to most damning — if you answer "rewrite" to the first one, you might still be wrong. If you answer "rewrite" to the last one, you are almost certainly right.
1. Is the language or framework still viable?
A codebase written in a still-maintained language with a still-active framework can almost always be refactored. Refactor.
A codebase written in a language whose runtime is end-of-life (Python 2, AngularJS, Rails 3) faces a forced migration regardless. The question is not whether you do work; it is whether the work is incremental or wholesale. Even here, refactor first — most "stuck on old framework" problems can be solved with a strangler-fig migration rather than a rewrite.
2. Is the domain model still correct?
Look at the core nouns in your codebase — the tables, the entities, the aggregates. Do they still match the business as it operates today?
If the domain model is right, even an ugly implementation can be refactored. The hard part of software is the model; the implementation is the easy part. Refactor.
If the domain model is fundamentally wrong — if the system was designed for one business and is now being used for another — refactor becomes very expensive. Every change has to bend the wrong abstraction around the right reality. Consider rewrite.
3. Can you write tests that cover existing behavior?
A refactor without tests is not a refactor; it is a rewrite without a plan. Before deciding, try to write characterization tests for the five most important user flows. If you can write them and they pass against the current system, you have a safety net for refactoring.
If you cannot write the tests — because behavior is too coupled to external services, because outputs are nondeterministic, because the code is so tangled that "behavior" is not separable — refactor becomes dangerous and rewrite becomes more attractive.
4. How long does a typical change take, and is it getting worse?
Measure the time from "we decided to add feature X" to "feature X is in production" for the last five non-trivial features. Then ask whether the trend is flat, slowly worsening, or accelerating worse.
- Flat or slowly worsening: normal aging. Refactor in place as part of feature work.
- Accelerating worse: the codebase has crossed into negative leverage — every new feature makes the next one harder. This is the most reliable signal that something structural must change. Whether that something is refactor or rewrite depends on questions 2 and 3.
5. Has the team turned over?
A refactor requires people who understand the existing system's intent. If everyone who built the original system has left, and the code has no tests and no documentation, you are not really refactoring — you are reverse-engineering from a corpse. At that point, a careful rewrite with the current team's understanding of the business often produces a better outcome than archaeology.
The third option nobody picks: strangler fig
Most "refactor vs. rewrite" conversations skip the option that is almost always best: the strangler fig pattern, named by Martin Fowler after a tropical tree that grows around a host and gradually replaces it.
You stand up a new system next to the old one. You route a small, well-bounded part of traffic to the new system — a single endpoint, a single page, a single background job. You verify it works. You expand the slice. The old system shrinks; the new system grows. At no point is the product frozen, and at no point is there a high-risk cutover day.
The pattern is harder than refactor and harder than rewrite, because it requires running two systems in parallel and routing between them cleanly. It is almost always worth it. Most of our audit-and-refactor engagements end up recommending some form of strangler-fig rather than either pure option.
Signals that justify a true rewrite
Strangler fig works for almost every case. The exceptions — situations where a true rewrite is the right answer — share a small set of properties:
- The runtime itself is the problem (an unsupported VM, a banned language, a deprecated platform that the cloud provider is sunsetting on a specific date).
- The domain model is fundamentally wrong and the system has become load-bearing for the wrong shape — for example, a single-tenant product being twisted into multi-tenant.
- The legal or compliance landscape has changed in a way that the existing architecture cannot accommodate (data residency, encryption, audit logging at a depth that requires re-architecture).
- The product is small enough — typically under ten thousand lines of meaningful logic — that a rewrite is bounded and cheap.
If none of those apply, the honest answer is almost always refactor or strangler fig.
The hidden costs of a rewrite
Teams that pick rewrite often underestimate three specific costs:
The bug bank
Every bug that has been reported and fixed in the old system is a piece of information about reality. Rewrites lose all of it. You will re-discover those bugs in production, one customer at a time, over the first eighteen months.
The integration surface
Old systems accumulate integrations — analytics events, webhooks, exports, internal admin tools, partner APIs. A rewrite has to re-implement every one of them or accept that something downstream breaks. Teams routinely scope the new system and forget that they are also signing up to rebuild forty-seven side integrations.
The morale tax
Rewrites take longer than predicted. Around month eight or nine, the new system is partially working and the old system is still in production, and the team is maintaining both. This is the morale low point of every rewrite. People leave. Plan for it.
A simple decision rule
If you want one rule of thumb: refactor unless the domain model is wrong or the runtime is dying. If you must replace, use strangler fig before you consider a full rewrite.
A true rewrite is justified roughly once in every ten "we should rewrite this" conversations. The other nine are an under-resourced refactor or an unwillingness to deal with a specific painful module. The right move there is usually to find the painful module and fix that one thing first.
FAQ
How do you decide whether to refactor or rewrite a codebase?
Answer five questions: is the language still viable, is the domain model still correct, can you write characterization tests, is change velocity getting worse, and has the original team turned over. Refactor unless the domain model is wrong or the runtime is end-of- life. If you must replace, prefer the strangler-fig pattern over a full rewrite.
How long does a typical software rewrite take?
The optimistic estimate is usually six months. The honest median for a non-trivial product is eighteen to twenty-four months, with a feature freeze on the existing product for at least twelve of those months. Plan accordingly.
What is the strangler-fig pattern?
Strangler fig is an incremental replacement pattern named by Martin Fowler. You stand up a new system next to the old one and route traffic to it piece by piece — a single endpoint, then a single page, then a single user segment — until the old system has no traffic left. It avoids the high-risk cutover of a rewrite while still letting you replace fundamental architecture.
When is refactoring the wrong choice?
Refactoring is the wrong choice when the domain model is fundamentally incorrect, when the runtime is end-of-life with a fixed sunset date, or when there are no tests and no original team available to clarify intent. In those cases, refactor effort becomes archaeology, and a careful rewrite or strangler-fig migration is usually the better path.
How much does it cost to refactor a codebase?
A focused refactor of a defined module typically takes four to eight weeks of senior engineering time. A whole-system refactor is harder to bound, which is exactly why strangler-fig is preferable — it breaks the work into shippable, four-to-six-week increments instead of one open-ended engagement.
Kirin runs codebase audits and refactor engagements for teams that have inherited a system slowing them down. We embed for four to eight weeks, write the audit, then do the work with your team — not for them.