Refactoring vs Rewriting a Monolith
Refactoring evolves the existing system in place, preserving behavior while changing structure; rewriting rebuilds it from scratch on a clean slate. Refactoring wins for almost every large, business-critical monolith because it keeps the system running, ships value continuously, and never loses the undocumented behavior the old code encodes. A full rewrite concentrates all the risk on one distant cutover and routinely fails. Rewrite only small, well-understood components — and even then, incrementally.
Part 2 settled the whether. This part is about the how — and it opens with the most seductive plan in all of software: the clean rewrite. Throw away the tangled old code, start fresh with everything you’ve learned, and build the system you wish you had. Almost everyone who has stared at a legacy monolith has wanted this. Almost everyone who has tried it has regretted it.
The choice is between refactoring — changing the structure of the existing system while preserving its behavior — and rewriting — rebuilding the functionality from scratch on a clean slate. For decomposing a monolith, this is the decision that most determines whether the program ships at all.
Why the rewrite is so tempting
The pull is real and worth respecting. The existing code is genuinely hard to work in. The original authors are often gone. A fresh codebase promises modern tooling, a clean architecture, and no legacy baggage. And there is a quiet, powerful bias underneath it: reading bad code is harder than writing new code, so rewriting feels faster than understanding what is already there.
That feeling is the trap. The old code looks simple to replace precisely because you cannot see what it does — and what it does is the problem.
What the old code actually contains
A monolith that has run a business for years is not just an implementation. It is a specification written in the only language that never lies — production behavior. Every strange conditional is a bug someone hit. Every odd default is a customer who complained. Every special case handles a real scenario the original design didn’t anticipate. Most of it was never written down anywhere else.
A rewrite throws all of that away and bets it can be rediscovered. It usually can’t — not on a schedule, and not before customers find the gaps for you. This is why the rewrite is not a fresh start; it is a second attempt to learn everything the business already knows, paid for again, in production.
Why full rewrites fail
The deeper problem is structural, not a matter of skill. A full rewrite concentrates every risk onto a single distant cutover, and that shape fails for reasons that have nothing to do with how good the team is:
- Feature parity is a moving target. The new system cannot ship until it matches years of accumulated behavior — but the old system keeps changing while you build, so the target moves away from you.
- Value arrives only at the end, if it arrives. The business waits years for the first usable delta and funds the whole effort on faith.
- The roadmap freezes. Two systems run in parallel; every new requirement has to be built twice or deferred. Competitors do not wait.
- The hidden rules surface late. The undocumented behavior shows up only when the new system gets it wrong in front of customers — after the sunk cost has already won the argument.
- The replacement over-reaches. Freed from the old system’s constraints, a rewrite tends to absorb every feature and abstraction the team always wanted. This is the classic second-system effect, and it makes an already optimistic schedule slip further while the clean slate that justified the rewrite fills back up with ambition.
This is not a niche risk. BCG found that up to 70% of digital transformations fail to deliver on their objectives (BCG, 2023), and the big-bang rewrite is the single most reliable way to land in that 70%. We treat the structural reasons in depth in why modernization projects fail; the short version is that the rewrite’s shape is the failure, not its ambition.
The third option: incremental refactoring behind a strangler facade
The good news is that you do not have to choose between “live in the mud forever” and “bet the company on a rewrite.” There is a third path, and it is the one this series is built on.
You refactor the system toward the target architecture one slice at a time, behind a strangler facade. The facade sits in front of the system and routes each request to either the old code or a newly built component. You carve out one slice of behavior, build its replacement, prove it equivalent to the original, and shift its traffic across. Then the next slice. The old monolith is gradually emptied of responsibility until nothing routes to it and it can be retired.
This gives you what the rewrite only promised:
- The system keeps running the entire time — no freeze, no cutover date to bet on.
- Value ships continuously — a working, modernized slice every few weeks, not a deliverable years away.
- Risk is distributed — being wrong about one slice is a contained, reversible event, not a catastrophe.
- The hidden rules are preserved — because each slice is proven to match the original’s behavior before it carries live traffic, not after.
The reversal worth keeping: a rewrite asks you to know everything up front; refactoring lets the system teach you as you go.
Note that “refactor” here is broader than its textbook meaning of restructuring without behavior change. Across a slice you may genuinely rebuild a component on a new stack — but you do it inside the incremental, parity-gated loop, so the rebuild is contained to one slice and proven before promotion. The unit of safety is the slice, not the whole system.
Refactor vs rewrite, side by side
Laid out against the axes that actually decide whether a modernization program survives, the two approaches barely resemble each other.
| Dimension | Incremental refactoring | Full rewrite |
|---|---|---|
| Risk shape | Distributed across small, reversible slices | Concentrated on one distant cutover |
| First value | A working, modernized slice in weeks | Nothing usable until the end, if it arrives |
| Roadmap | Keeps moving on the same pipeline | Frozen while two systems run in parallel |
| Undocumented behavior | Preserved, parity-proven before each slice ships | Rediscovered late, often by customers in production |
| Cutover | Gradual, one slice at a time | One all-or-nothing switch |
| Rollback | A routing flag per slice | All-or-nothing, where it exists at all |
| Scope it fits | Large, business-critical, imperfectly understood monoliths | Small, well-understood components a single team can rebuild fast |
The pattern is hard to miss. Refactoring is not the timid option, it is the one that keeps the decision reversible at every step. A rewrite trades that reversibility for a clean slate you cannot cash until the very end, which is exactly when it is most expensive to discover you were wrong.
The honest case for a rewrite
Dogma would be its own failure, so here is where a rewrite is the right call:
- Small, well-understood components. If a single team can rebuild a component cleanly in weeks, and its behavior is genuinely understood, the overhead of incremental coexistence can cost more than the risk it removes. Don’t build elaborate scaffolding to replace something one engineer could rewrite over a long sprint.
- A genuinely dead platform with no coexistence path. If the runtime is at hard end-of-life and cannot run alongside a replacement at all, incremental coexistence may be unavailable. (Check the runtime’s status with our software end-of-life checker before assuming this.)
- Behavior you want to discard. Occasionally the old behavior is genuinely wrong and the business wants it gone. Then preservation is not the goal — but you still ship the replacement incrementally.
Even in these cases, the unit stays small and the cutover stays gradual wherever it can. The mistake is not choosing to rebuild a component; it is choosing to rebuild the whole system at once because the clean-slate story is seductive. The teams who make that choice are not naive — the framing simply hides the risk until it is too late to recover.
Where this leads
The decision is made: evolve the system incrementally rather than rebuild it wholesale. That raises the practical question this series exists to answer — how, concretely, do you carve a running monolith into services without breaking the business that depends on it? Part 4, How to Migrate a Monolith to Microservices, lays out the incremental playbook end to end: where to cut first, how to keep the seam stable, and how each slice gets to production safely.
Frequently asked questions
- Is it better to refactor or rewrite a legacy system?
- Refactor, for almost any large or business-critical system. Refactoring preserves the behavior the business depends on while you change the structure underneath it, and the system keeps running the entire time. A rewrite throws away working code along with the thousands of undocumented edge cases it encodes, then asks the business to wait years for a replacement that may not match. The exception is a small, well-understood component a single team can rebuild quickly.
- Why do rewrites fail so often?
- Because they concentrate all the risk on one distant cutover. The replacement has to reach feature parity with years of accumulated behavior before it can ship anything, the roadmap freezes while two systems are maintained in parallel, and the rules nobody wrote down surface only when the new system gets them wrong in production. Meanwhile the original keeps changing, so the target moves. The plan and reality diverge — and that divergence is the norm.
- What is the strangler pattern?
- The strangler pattern is an incremental alternative to a rewrite. A facade routes traffic to either the old system or new components; you replace one slice of behavior at a time behind it, proving each slice equivalent before it carries live traffic, until the old system is emptied of responsibility and retired. It gives you the clean target a rewrite promises without the all-or-nothing cutover that makes rewrites fail.
- When is a rewrite actually the right choice?
- Rarely, and only at small scale. A rewrite makes sense for a small, well-understood component a single team can rebuild in weeks, where the overhead of running old and new in parallel costs more than the risk it removes. It is sometimes forced when the runtime is at hard end-of-life and nothing can run alongside a replacement, or when the business genuinely wants to discard the old behavior rather than preserve it. Even then, keep the unit small and the cutover gradual. The mistake is never rebuilding one component, it is rebuilding the whole system at once because the clean slate looks faster than it is.
- What is the second-system effect?
- The second-system effect is the tendency for a replacement to become over-engineered, absorbing every feature and abstraction the team wished the original had. A rewrite is especially prone to it because the clean slate removes the constraints that kept the first system lean. The result is a replacement that is larger and later than planned, which compounds the rewrite's core problem of delivering no value until the very end. Incremental refactoring resists it because each slice has to prove itself in production before the next one starts.