When to Refactor and When to Rewrite
Refactoring improves the internal structure of code without changing its observable behavior. Rewriting replaces the code entirely with a new implementation. The decision between them is not about code quality. It is about cost, risk, and what the existing code knows that you do not. I lean heavily toward refactoring, and I push for rewrites only when I can show the math that justifies them.
Written by Yashveer Singh, founder of Yashveer Labs.
What you actually need to know
- Refactoring changes the structure without changing the behavior. Rewriting changes both.
- Most code that engineers want to rewrite can be refactored. The instinct to rewrite is often about comfort, not cost.
- The rewrite is justified when the cost of maintaining the existing code exceeds the cost of replacement within eighteen to twenty-four months.
- Tests before refactoring, not after. You need the safety net before you move the furniture.
- Scope creep kills refactors. Define the module boundary before you start and hold it.
- The strangler fig pattern is the right path for any system that handles live traffic.
| Path | Risk | Time to benefit | When to choose |
|---|---|---|---|
| Small targeted refactor | Low | Weeks | One module is slow to change or unreliable |
| Module rewrite with interface parity | Medium | Months | Module is beyond salvage but the rest is fine |
| Strangler fig migration | Low-medium | Quarters | Live system with multiple integrations |
| Big bang rewrite | High | Never on schedule | Almost never justified |
| Greenfield parallel build | High | Very long | Only for isolated subsystems with no user traffic |
The core argument
The question of refactor versus rewrite comes up in almost every engineering team that has been building for more than two years. The codebase has accumulated decisions that made sense at the time and do not make sense now. Someone on the team says "we should just rewrite this." The conversation that follows is usually emotional rather than analytical.
The emotional pull toward rewriting is understandable. The existing code has scars. Every engineer who has worked on it carries the memory of the bugs it caused and the workarounds it required. The idea of starting clean, without those scars, feels like relief. The problem is that rewrites are almost never the relief they promise. The new code will have its own scars. The team will carry knowledge about the problem domain that only existed in the old code. The schedule will slip. The business will wait.
The analytical question is simpler. How much is the existing code costing per quarter in engineer time, incidents, and slow delivery? How much would it cost to replace? At what point does the replacement pay back its cost? If the break-even is within eighteen to twenty-four months, the rewrite deserves a real evaluation. If the break-even is three years out, the refactor is almost always the better call, because the rewrite estimate will be wrong by the time it is done.
Refactoring is not settling. It is precise surgery on the parts that are creating the most drag, while leaving the parts that work alone.
How to make the decision
The cost test
Before any rewrite conversation, put a number on the current maintenance cost. Hours per sprint spent on workarounds, bugs traced to this module in the last six months, features slowed because the module resists change. Translate to dollars. Then estimate the rewrite cost in engineer-weeks and translate that too.
If the monthly maintenance cost times eighteen months is greater than the rewrite cost, the rewrite is defensible. If not, the refactor is the right call.
The knowledge test
Every line of code that has been in production for more than a year contains implicit knowledge. Edge cases the team discovered through bugs. Business rules that were added quietly during an incident. Behaviors that customers depend on without knowing they do.
Before approving a rewrite, ask how much of that knowledge is documented anywhere other than the code. If the answer is "very little," the rewrite will rediscover those edge cases the hard way, in production, after the old code is gone.
The scope test
If the code that needs replacing is a module with a defined interface, a rewrite is more tractable. If the code is a cross-cutting concern that touches everything, a rewrite will expand until it consumes the entire codebase. The strangler fig is the right answer for cross-cutting concerns. The targeted rewrite is right for bounded modules.
What it costs
| Decision | Engineering cost | Time to value | Risk of delay |
|---|---|---|---|
| Targeted refactor, one module | Low, days to two weeks | Weeks | Low |
| Module rewrite with tests | Medium, weeks | Months | Medium |
| Strangler fig on a subsystem | High, quarters | Quarters | Low-medium |
| Full application rewrite | Very high | Often never on schedule | Very high |
The full application rewrite rarely delivers on its original timeline. The average is two to three times the estimate. Plan accordingly or do not plan it.
What to look for before approving a refactor or rewrite
- A defined scope boundary with named modules in and out.
- Tests around the existing code before any structural change.
- An interface contract for the module that will stay stable during the change.
- A rollback path if the refactor introduces regressions.
- A team member who owns the work from start to finish without splitting focus.
- A defined done condition, not "when it feels clean."
- A communication plan for the rest of the team about what will and will not change.
Expert opinion
The refactor versus rewrite decision is almost always made emotionally and justified analytically afterward. Engineers who want to rewrite will find the numbers to support it. Engineers who want to refactor will find the numbers to support that. The discipline I hold to is: show me the maintenance cost first, then show me the replacement cost, and we will do the math together. The math almost always points to refactor.
>
Yashveer Singh, founder of Yashveer Labs
How this played out on a real project
A client came to me with a payment processing module that the team wanted to rewrite. The stated reason was that the code was "impossible to understand." The module had been in production for three years and processed several million dollars per year without a major incident.
We ran the two-day tech debt audit on the module. The actual cost of maintaining it was four hours per sprint, mostly because there were no tests and every change required manual verification. The rewrite estimate was six weeks. At the team's rate, that was about forty thousand dollars.
The math said: four hours per sprint at their blended rate was around two thousand dollars per month. The rewrite would pay back in twenty months. That was inside our eighteen-to-twenty-four month threshold, but only barely. We recommended a refactor instead: add tests first, then extract the pure logic, then improve the integration layer. Three weeks of work, not six. The module became maintainable without the risk of a full replacement.
The team initially pushed back. Two months after the refactor was done, they admitted it was the right call. The module still runs without incident. The code is now comprehensible. The rewrite would have been a distraction.
For the companion decision on how to structure the actual cleanup work, the refactor sprint post covers how to scope and run a focused sprint without derailing the delivery cadence.
Common mistakes
- Starting the rewrite without testing the existing code first. The new code will miss behaviors that only existed in undocumented edge cases.
- Letting scope creep expand the refactor into an accidental rewrite. Hold the module boundary.
- Choosing to rewrite because the code is ugly, not because it is costly. Ugly code that works is not a business problem.
- Running the refactor without a defined done condition. "Cleaner" is not a done condition. "Tests pass, module deploys independently, no cascading changes required" is.
- Assigning the refactor to the most junior engineer because senior engineers are on feature work. Structural changes require structural judgment.
- Not communicating the change to the rest of the team. Surprises in shared modules break other people's work.
- Treating the refactor as invisible infrastructure work with no visibility in the sprint. It belongs on the board, with a ticket, and a done definition, the same as any feature.
A three-week refactor plan
- Days one to three. Define the module boundary. Write down the interface contract. Add tests that freeze the current behavior, even if they are ugly.
- Days four to eight. Refactor the internal structure. Do not change the interface. Run the tests after every meaningful change.
- Days nine to twelve. Clean up the integration points. Update the tests to reflect the improved structure.
- Days thirteen to fifteen. Deploy to staging, run the full test suite, deploy to production. Monitor for forty-eight hours.
For further reading on the related migration question, big bang vs gradual migration gives the structured decision map for when an architectural change is large enough to require a migration strategy rather than a sprint.
Frequently asked
About the author and why it matters
Yashveer Singh wrote this. I run Yashveer Labs out of New Delhi. The work I take on tends to come from founders who have been burned by an agency, a freelancer, or their own ambition. I do not promise miracles. I promise that the system will be online, the code will be readable, and the next engineer who touches it will not curse me. That is rarer than it should be.
Posts that line up with this one.
- Tech Debt and Refactoring
Migrating From Express to Fastify or NestJS or Beyond
Express still works but it shows its age in production. Here is when to migrate, which framework to migrate to, and how to do it incrementally without breaking the application that customers depend on.
- Tech Debt and Refactoring
Migrating From REST to GraphQL: A Strategic Read
GraphQL solves real problems but introduces its own. The migration from REST to GraphQL is not a performance upgrade; it is an architectural shift. Here is when it is worth it and how to do it without breaking existing clients.
- Tech Debt and Refactoring
Mutation Testing: A Discipline Worth Considering
High code coverage does not mean good tests. Mutation testing reveals whether your tests actually catch bugs. Here is what it is, when it adds value, and how to introduce it without adding meaningless overhead.
- Tech Debt and Refactoring
Refactor Stories That Killed a Startup
Refactoring is necessary and valuable. It is also one of the most reliable ways to destroy momentum at the wrong moment. These are the patterns that turn a reasonable engineering goal into a business catastrophe.