Rewrite or Refactor: How to Decide
08 Jul 2026“Let’s just rewrite it” is usually frustration talking, not a real engineering decision. Frustration is a terrible input to an architecture choice, so here’s a framework that doesn’t run on how annoyed you are with the current code.
The question that actually matters
Not “is this code bad,” but: can you make the change you need one piece at a time, with the system working at every step? If yes, refactor. If genuinely no, rewrite. Almost everything is a yes, which is why rewrites are almost always the wrong call — but “almost always” isn’t “always,” so it’s worth actually checking instead of assuming.
Here’s the path that check actually follows:
flowchart TD
A["Need to change the system"] --> B{"Can you ship it one\npiece at a time?"}
B -->|Yes| C["Refactor"]
B -->|No| D{"Is the foundation wrong,\nnot just messy?"}
D -->|Just messy| C
D -->|Yes, wrong| E{"Can old and new run\nin parallel during cutover?"}
E -->|No plan yet| F["Write the cutover\nplan first"]
E -->|Yes| G["Rewrite with a\nstrangler-fig cutover"]
When refactoring wins (most of the time)
- The core domain model is basically right, even if the implementation is messy. Bad naming, duplicated logic, and missing tests are all refactorable. A wrong data model dressed up in clean code is not.
- You can ship value incrementally. If you can improve one module, deploy, verify, and move to the next, you get continuous feedback and the system never stops working. A rewrite gives you no feedback until the very end, when it’s too late to cheaply change course.
- The team doesn’t have to relearn the system. Institutional knowledge about weird edge cases — the one customer whose data format doesn’t match the schema, the timezone bug that was fixed for a reason nobody remembers — lives in the current code and the current team’s heads. A rewrite tends to reintroduce every bug that was already fixed once, because nobody wrote down why the weird-looking code existed.
When a rewrite is actually justified
- The foundational technology is the problem, not the code on top of it. A framework that’s unsupported, a language with no available hires, a database that can’t scale to your next order of magnitude — no amount of refactoring fixes a foundation.
- The domain model is fundamentally wrong, not just messy. If the code models “one user has one account” and the business now needs “one user has many accounts across many orgs,” that’s not a refactor, that’s a different shape of system.
- You can genuinely run the old and new systems in parallel during the transition, with a real plan for migrating data and traffic incrementally — not “flip a switch on launch day.” A rewrite without an incremental cutover plan is a rewrite that will be cancelled six months in, with nothing shipped.
The trap in the middle
The most common failure mode isn’t picking wrong — it’s starting a rewrite and not finishing it. Rewrites take longer than estimated almost without exception, because the old system’s behavior includes years of undocumented edge-case handling that only becomes visible when the new system breaks on them. Meanwhile the business doesn’t stop, so the old system keeps getting patched too, and now you’re maintaining two systems while shipping neither.
Before committing to a rewrite, write down the incremental cutover plan first — which slice of traffic moves first, how you’ll verify parity, what the rollback looks like. If you can’t write that plan, you don’t have a rewrite plan, you have a rewrite wish.
A test that cuts through the frustration
Ask: if I had to keep the current codebase forever and just make it good, could I? If the honest answer is yes — it would take real effort, but the shape underneath is sound — that’s your answer, and it’s cheaper and safer than it feels right now while you’re annoyed with the code.
If the honest answer is no, the current shape cannot support what the business needs no matter how much you clean it up, that’s a real rewrite case. Just go in with a strangler-fig plan — new system built alongside the old, traffic migrated feature by feature — not a big-bang cutover that bets the whole project on one launch day.