Refactor or rewrite a system: what each path means in practice
Refactoring means changing the internal structure of the code without changing what the system does for the user. You reorganize, separate responsibilities, swap libraries, and create tests, all while the system stays in production. Legacy code refactoring happens in small steps, and each step can go live.
Rewriting means building a new system alongside the old one and swapping one for the other at the end. There is a cutover day, and until then the investment produces nothing the user can see.
This difference changes the risk profile. In a refactoring, mistakes show up early and are small. In a full rewrite, mistakes tend to show up at cutover, when almost the entire budget has already been spent.
It's worth noting that changing the language doesn't force you to rewrite. You can change the framework, architecture, and interface while keeping the language and much of the logic. Many rewrite debates end once you separate what is bad (the structure) from what is good (the rules the company spent years getting right).
Why a full rewrite usually goes wrong
The first risk is losing business rules hidden in the code. A system that has been in use for ten years carries hundreds of decisions nobody documented: the discount that doesn't apply to a certain customer, the rounding that finance requires, the special case created by an old contract. Nobody remembers them until the new system gets them wrong.
The second is freezing the product. While the team builds the new version, the old system still needs fixes and improvements. Either you keep two teams, or you stop evolving the product for months. The business doesn't wait, so changes end up being made twice, in the old system and in the new one.
The third is bad estimation. It's easy to estimate what you can see (screens, reports, forms) and hard to estimate what you can't: integrations, migration of messy data, permissions, nightly jobs. The most common mistake we see is budgeting for the visible part and discovering the rest halfway through.
There is also a fourth, subtler risk: the new-system effect. The team believes it will get everything right this time, and the scope grows. The rewrite becomes a chance to include everything that was left out, and the project stops being a replacement and turns into a new product, with the timeline and cost of a new product.
When rewriting software from scratch really makes sense
We advocate a full rewrite in only a few cases, and all of them have verifiable criteria.
Even in those cases, we recommend narrowing the scope to what the business uses today. Rewriting everything the old system did, including what nobody uses anymore, is the most expensive way to start.
- The technology is no longer supported and there is no upgrade path: a language or platform with no security fixes, no professionals available in the market, or no compatibility with what the company needs to use.
- The architecture blocks the business: for example, a system designed for a single customer that needs to become multi-company, or a design that accepts no integration with anything and blocks any partnership.
- The system is small: if rebuilding takes only a few weeks and the rules fit in the heads of the people who use it, the cost of understanding the old code can be higher than the cost of writing it again.
- The system is no longer used the way it was designed: the company's process has changed so much that what exists solves a problem that no longer exists.
When refactoring is the best choice
Refactoring is better when the system works, users depend on it, and the real problem is that changing it has become expensive or scary. This is the most common scenario: the code runs, but every change takes three times as long and breaks something far away.
In these cases, the gain comes from attacking the pain points: the part that changes every week, the one that always breaks, the one only one person understands. You don't need to fix everything. A well-organized module with tests already reduces the cost of change in that area.
Another sign in favor of refactoring is when the business knowledge lives in the code and nowhere else. If documentation doesn't exist and the team that built it has left, the code is the only source of truth. Throwing it away means throwing away that memory.
If you want the full picture of how this work is organized, our guide to modernizing legacy systems covers the options from end to end.
The middle path: incremental migration and the strangler pattern
Between refactoring from the inside and rewriting everything, there is a third path, and it's the one we recommend most for medium and large systems: incremental migration. The best-known pattern is called the strangler, or strangler fig, named after a plant that grows around a tree until it replaces it.
In practice, you place a layer in front of the old system that decides where each request goes. You take one module, rebuild it in the new technology, and redirect only that traffic. The rest stays on the old system. You repeat this, module by module, until the old system receives nothing and can be shut down.
The advantages are concrete: each module goes to production on its own, errors stay contained, you can roll back by sending traffic back, and the product keeps evolving. You also learn from the first modules and adjust the estimates for the next ones with real data.
The point to watch is the database. If the new and old systems write to the same tables without care, they get in each other's way. Define who owns each piece of data at each stage and plan the synchronization. We go into this kind of care in how to migrate an old PHP system without stopping operations.
This is the format we follow in our legacy system modernization practice: small steps, the system live the whole time, and ongoing maintenance afterward.
Questions to ask when deciding whether to refactor or rewrite
Before choosing, answer these questions honestly. If several answers are vague, the problem is a lack of information, and the first step is a diagnosis, not a decision.
- Does the current technology still receive security fixes, and are there professionals available in the market?
- Can we list the business rules the system applies, or do they exist only in the code?
- Are there automated tests? Without them, any change, whether refactoring or rewriting, is made blind.
- What is holding the business back today: the cost of each change, instability, lack of integration, or an architecture limit?
- Can we keep the product evolving for months without delivering anything new? If not, a full rewrite is off the table.
- How many modules does the system have, and which ones are independent? The more separable they are, the more viable incremental migration is.
- Is the person who understands the system still at the company? If they leave in the middle of the project, what happens?
- What is the cost of getting it wrong? A system that halts operations and an internal report call for different appetites for risk.
A six-step decision roadmap
This is the roadmap we use to reach a decision that holds up in front of the board.
Step six is the one that prevents the most regret. A small, real sample shows, with data from your own system, how much it costs to extract a rule, migrate a piece of data, and test the result. That is worth more than any estimate made just by looking at the screen. If you want to see how this looks before committing, you can ask for a free prototype. And to get an order of magnitude for the investment, see what determines how much it costs to modernize a legacy system.
- Step 1: define the business problem in one sentence (for example, launching a feature takes months because of the code), not the technical problem.
- Step 2: take inventory: modules, integrations, databases, scheduled jobs, and who uses each part.
- Step 3: check the rewrite criteria: unsupported technology, architecture that blocks the business, small system. If none applies, rule out the full rewrite.
- Step 4: gather the business rules by talking to users and reading the code in critical areas, and turn them into tests.
- Step 5: rank the modules by pain and by independence. Start with what hurts and can be separated.
- Step 6: run a pilot on a small module, measure effort and risk, and only then decide the format and budget for the rest.
Common mistakes when deciding
The most common mistake we see is deciding based on the technology instead of the problem. Someone wants to use the trendy tool, and the rewrite becomes the justification. The second is underestimating data migration, which is usually the slowest part and the one that surprises the most.
It's also common to treat the decision as binary. Refactoring one module, rewriting another that is small, and strangling the rest is a perfectly legitimate combination. And finally, there is planning the modernization without providing for maintenance afterward: the new system ages just like the old one if nobody takes care of it.
From our experience
At SKAD, we refactored the SyncStop, 4Car, KaraokeApp, The Bee App, and DEM (equipment inspection) systems with the help of AI. All of them were plain PHP with jQuery and Bootstrap, and the apps used Cordova.
Today they run on PHP 8.3 with Slim 4 for the API, React with Vite on the web, and React Native with Expo in the app. We kept the PHP language and changed the framework and architecture, without starting from a blank page.
After the refactoring, maintaining these systems became lighter. That is why, when the system works and the problem is the structure, our inclination is to refactor and migrate in stages rather than start everything over.
Frequently asked questions
Is rewriting a system from scratch always a bad idea?
No. It makes sense when the technology is no longer supported, the architecture blocks the business, or the system is small. Outside of those cases, the risk usually outweighs the benefit.
How long does it take to refactor or rewrite a system?
It depends on the size, the number of integrations, the quality of the data, and whether tests exist. That's why you should be wary of any timeline given without looking at the system; a pilot on one module provides a real basis.
What is the strangler pattern?
It's a form of migration in which a layer in front of the old system redirects traffic, module by module, to the new version. The old system is gradually emptied out until it can be shut down.
Can you refactor a system without automated tests?
You can, but with more risk. The best approach is to create tests for the critical rules before touching anything, so you know whether the change preserved the behavior.
Refactor or rewrite: which is cheaper?
In the short term, a rewrite looks simpler, but the real cost usually shows up in forgotten rules, data migration, and time without evolving the product. Incremental refactoring spreads out the spending and lets you stop or adjust course.
Conclusion
Before approving a rewrite or a refactoring, ask for a diagnosis of your current system: an inventory of modules, critical rules, the state of testing, and a pilot on a small piece. With that in hand, the decision stops being a bet. If you'd like, our team can run this diagnosis with you: message us on WhatsApp using the button below and tell us what your legacy system does today and where it gets stuck.