In our judgment, the answer to rewrite vs refactor is to refactor by default. Rewrite only when the thing blocking you is the foundation: a data model that can't express your business, or a platform you can't hire for or patch. Even then, replace it in slices while the product keeps running, rather than stopping to rebuild. For a team of 10-40 engineers, the harder part is deciding who does the migration while features still ship. More on this theme is in our engineering management guides for CTOs and VPs of Engineering.
What's the difference between a refactor, a rewrite and a strangler migration?
Refactoring, as defined on Martin Fowler's refactoring.com, is a disciplined technique for restructuring existing code by altering its internal structure without changing its external behaviour. It's a series of small, behaviour-preserving transformations, and the system stays fully working after each one.
A rewrite is the opposite shape: you build a replacement beside the old system and switch over when it's done. In a rewrite, little of the new system is usable until most of it is built.
A strangler migration sits between them. Fowler's 2024 write-up of the strangler fig application, named after the strangler figs he saw in Queensland rain forests in 2001, describes growing the new system around the old one and moving functionality across a piece at a time. Fowler says he and his colleagues prefer this gradual modernisation to a single cutover.
What does the "never rewrite" argument say, and where does it stop applying?
Joel Spolsky made the best-known case in his 2000 essay "Things You Should Never Do, Part I". It's an opinion piece, and a quarter of a century old, but it's still the reference point. He called throwing away a whole program "a dangerous folly" and described Netscape's rewrite as the single worst strategic mistake a software company can make. His underlying claim is that it's harder to read code than to write it, so old code looks worse than it is, and the urge to rewrite often comes from not understanding it.
Fowler's experience points the same way from a different angle. He writes that the simple plan of replacing a legacy system in one go will "go down in flames most of the time" in what he and his colleagues have seen, and that "the reduced risk and earlier value from the gradual approach outweigh its costs." That's his observation, not a measured failure rate, and none of the sources here publishes one.
Our reading: both arguments are against stop-the-world rewrites of a product that works and earns money. Neither says you can never replace a part. A startup that rips out its billing module or its data layer over three months is doing something quite different from a startup that freezes the roadmap for a year.
Which signals justify a rewrite, and which don't?
The table below is our judgment, built on the sources above. It isn't a scoring model. Use it to check whether the pain you're feeling is a code problem or a foundation problem.
| Signal | What it usually means | Default |
|---|---|---|
| Ugly code, but the data model is sound | The structure is messy, not wrong | Refactor |
| Slow builds, no tests, fear of every change | Missing safety net | Refactor, and add characterization tests first |
| The data model can't express the business | The foundation is wrong for what you sell now | Replace that part |
| A platform you can't hire for or patch | Every month raises the cost of staying | Replace it by strangling |
| One subsystem hits a scaling ceiling | A local problem | Strangle that subsystem only |
| Product direction changed | A scoping problem, not a code problem | Re-scope first |
| The team wants to rewrite because they didn't write it | Spolsky's point: unfamiliar code looks worse than it is | Refactor, and read the code first |
The first two rows are where most startups actually sit. If your tests are thin, a rewrite doesn't fix that. It moves the problem into new code with new bugs and no tests either. For how to fund the cleanup work, see how to budget for technical debt paydown.
Why is the strangler fig usually the answer when a rewrite is justified?
Microsoft's Azure Architecture Center describes the mechanism. A façade, which is a proxy, sits in front of the old system and routes each request to either the legacy code or the new code. You shift traffic across incrementally. When nothing depends on the legacy system any more, you decommission it and remove the façade. Azure says to use the pattern when the original system can keep running for an extended period.
The AWS Prescriptive Guidance page on the same pattern adds the part that matters for a team under feature pressure: build new features in the new system, and continue to fix bugs in the monolith so the application stays stable. AWS also notes that adding new features during a big-bang refactor is extremely hard or even impossible. The page frames the pattern around splitting monoliths into microservices, and we're not making that argument here. The generic points hold for any rewrite.
The sources list the costs, and a CTO should read them before committing:
- Both systems often need to reach shared data stores, so you have to decide who owns which data during the transition.
- Calls between the old and new systems need an anti-corruption layer so the old model doesn't leak into the new one.
- The façade must not become a single point of failure or a bottleneck.
- The transitional architecture has a temporary infrastructure cost.
Azure also says when not to use it: you can't intercept requests, you have no access to the legacy source code, the system is small enough that replacing it outright is simple, or you need to decommission the original quickly. AWS adds that you need the codebase in hand, and that for small applications with low refactoring complexity a rewrite might be more efficient. If your whole product is small enough that replacing it outright is simple, rewrite it and move on.
Fowler breaks the work into four activities, credited to Ian Cartwright, Rob Horn and James Lewis: understand the outcomes you want, decide how to break the problem up, deliver the parts, and change the organisation so this can go on. He's clear that it's an ordering of concerns, not a sequence of phases. In our judgment, the fourth one is the easiest to skip, and it leads to staffing.
How do you staff a migration without freezing features?
No source we've read says which team shape wins, so what follows is judgment. There are two workable shapes.
| Shape | Who owns what | Main risk | Fits when |
|---|---|---|---|
| Separate migration crew | The core team keeps shipping features on the legacy system or through the façade. A small crew owns the façade and moves slices. | Crew and core team drift apart, and the crew lacks domain knowledge | The core team is full of people who know the product and can't leave it |
| Core team migrates | The core team does the slices. A smaller group holds feature delivery. | Feature velocity drops visibly, and the roadmap needs renegotiating | The knowledge lives in a few heads and the roadmap can flex |
Whichever you pick, these rules of thumb are our judgment too:
- Put at least one person who knows the legacy domain on the migration crew. A crew made only of newcomers rebuilds the old system's mistakes, because nobody remembers why the odd rule is there.
- Don't put the crew on a hard date for a big-bang cutover. That recreates the rewrite you were trying to avoid.
- Protect on-call and bug-fix capacity. AWS's advice to keep fixing bugs in the monolith only works if someone is assigned to do it.
- Keep slices small enough to ship weekly, so a bad slice costs days, not a quarter.
- Give the façade one owner. A shared façade becomes nobody's problem.
On size: for a team of 10-40 engineers, the migration crew is a few senior engineers, not the whole team. We won't put a number on it, because it depends on how many people can work on the legacy code without stepping on each other.
If the crew is a gap rather than a reshuffle, remember that adding people mid-project has its own traps, Brooks's law among them.
What if the code is inherited or AI-built?
The decision framework above holds, but the starting conditions differ, and each case has its own guide.
If an agency built the product and the handover is thin, you're missing documentation, credentials and context, and the first job is to find out what you own. Taking over a codebase from a failed agency covers that.
If the product started as an AI-built prototype, the usual problems are security findings and missing structure, and the rebuild question looks different. Taking an AI-built prototype to production deals with it.
If the project is stalled or failing, not just aging, you're deciding under deadline pressure with a different set of questions. Rescue, restart or rewrite for a failing project is the place to start.
What this means for your rewrite decision
The decision is two choices: rewrite, refactor or strangle, and whether your core team or a separate time-boxed crew carries the migration.
Start by writing down the one thing that blocks you. If it isn't the data model or a platform you can't hire for, refactor and add tests first. If it is, pick the first slice that unblocks revenue or hiring, not the slice that's most annoying. Decide crew versus core team before the first commit, name the façade owner, and set a review point after each slice where you can stop or change course.
If the gap is people with the right seniority for a few months, HighCircl matches senior engineers in 72 hours with no minimum hour commitment, which suits a time-boxed crew. Next, read whether adding engineers to a late project helps before you size that crew.
FAQ
Should you rewrite or refactor?
Refactor by default. Rewrite only the part that blocks you, when it's the data model or a platform you can't hire for or patch, and do it in slices. A full stop-and-rebuild rewrite of a working product is the case both Spolsky (as opinion) and Fowler (from experience) warn against.
What does refactor mean?
Refactoring means restructuring existing code without changing what it does from the outside. Fowler's refactoring.com describes it as a series of small, behaviour-preserving transformations that keep the system fully working after each step.
What is the strangler fig pattern?
It's a migration approach where a façade routes requests to either the old or the new system, and functionality moves across piece by piece. Once nothing depends on the legacy system, you retire it and remove the façade. Fowler named it after strangler figs that grow around a host tree.
Can you rewrite while still shipping features?
Yes, if you migrate in slices. AWS's guidance is to build new features in the new system and keep fixing bugs in the old one. A big-bang rewrite makes shipping features extremely hard or even impossible, per the same guidance.
