The project nobody wanted to stop
After a merger there is always the same moment. Two companies, two infrastructures, two codebases, two ways of working. And at some point someone at C-level says: we have to consolidate this. We have to capture synergies. We need one unified platform.
What followed was a rewrite. Completely new. The entire SaaS infrastructure, uniformly in Go as microservices. Around 50 developers, spread across teams around the world, were meant to build a new architecture in one step, learn a new language and at the same time not put ongoing operations at risk.
Six months later the project was cancelled. A new CEO had different priorities.
The system it was supposed to replace is still running today.
Why rewrites are so tempting
The logic behind a full rewrite is always the same and it always sounds reasonable. The existing code has grown over years, is hard to maintain, full of technical debt. New requirements can only be integrated with effort. The team loses time on legacy code instead of working on features. A fresh start would make everything better.
That is not wrong. Grown systems have real problems. Technical debt is real. And sometimes the wish for a clean sheet of paper is entirely understandable.
The problem is not the diagnosis. The problem is the treatment.
A full rewrite is the most radical possible answer to a problem that in most cases tolerates a gradual solution. And radical answers carry radical risks that get systematically underestimated during planning.
What really happens when you start over
The first thing a rewrite underestimates is how much knowledge sits in the existing system without being documented anywhere. Edge cases that turned up at some point and were quietly fixed. Business logic that lives in a thousand small decisions. Integrations with external systems that behave differently than their documentation claims. That implicit knowledge does not have to be worked out once during a rewrite. It has to be worked out again and again, every time the new codebase runs into the same problems the old system had already solved.
That alone would be hard enough. In the project I lived through, it was not only a rewrite: a new language was introduced, a new architecture established and a global team coordinated that had never worked together this way. Each of those on its own would have been a challenge. Together they multiplied each other in a way that shows up in no project plan.
Go was new for many of the developers. That costs time, not only to learn the syntax but to understand idiomatic patterns, the concurrency models, the operational quirks that only become visible in production. Microservices as an architecture meant that decisions which could previously be made locally suddenly required cross-team alignment. And 50 developers in different time zones meant that this alignment cost hours every day that never went into code. From the start, the speed of the project was not limited by technical problems. It was limited by coordination overhead.
And then there is time. A running system delivers value every day. A rewrite delivers nothing for months. The longer it takes, the wider the gap grows between what the old system can already do and what the new one still cannot. And the harder it becomes to justify the rewrite internally, in front of a management that wants to see results, in front of a new CEO who does not know the project and does not have to defend it.
The organizational overhead nobody plans for
What planning documents usually leave out is the friction that appears when many people work towards a blurry goal. A full rewrite is by definition a project with high uncertainty. The requirements are not fully known because they are buried in the existing system. The architecture evolves during the project. Decisions get made, revised, made again.
In a small team that is manageable. In a team of 50 developers across several time zones, every ambiguity turns into a blocker. Alignment meetings eat time. Misunderstandings appear because the communication bandwidth between time zones is limited. Teams wait for each other.
On top of that comes the political dimension. After a merger, teams fight for influence over the new architecture. Decisions are made not only on technical criteria but on organizational ones. That slows everything down further.
And then a new CEO arrives.
Why the old system survives
The system it was supposed to replace is still running today. That is neither coincidence nor failure. It is the logical consequence of a system that was optimized over years for exactly the problems it solves every day.
Grown systems are often ugly. They carry technical debt. They are hard to understand for someone joining the team. But they run. They deliver value every day. And they hold implicit knowledge that no rewrite can rebuild in a year.
That does not mean legacy systems should never be modernized. It means full rewrites are the wrong answer in most cases.
What works instead
The alternative to a full rewrite is not standing still. It is incremental modernization with clear boundaries and measurable results, and I have seen how well that can work.
In another project, a new team was carved out of a large existing organization. The task was the one many teams know: take over a grown PHP codebase and modernize it. The difference was the approach. Instead of rewriting everything, I took over the existing codebase and started to modularize it step by step. Dependencies were identified and untangled. Logic was encapsulated. Modules were decoupled until they were testable and deployable on their own.
The goal was not perfection but the ability to move. Every module with clean boundaries could be migrated to PHP 8 independently without putting the rest of the system at risk. The system that had previously run as a monolith on PHP 7.1 was lifted onto a modern base step by step, without a single big cut, while it kept running in production and delivering value the whole time.
On paper that took longer than a rewrite. In practice it was faster because nothing ever came to a complete stop. After three months the first modules were already running on PHP 8. After six months most of the critical logic had been migrated. There was never a moment where management could have asked: when are we finally going to see results? The results were visible every week.
That is the fundamental difference to a full rewrite. An incremental approach spreads the risk, never puts the running system at risk and delivers value continuously while the modernization is under way. It is politically more resilient because it does not wait for one big moment that never comes; it delivers small moments every month that are hard to ignore.
What the project actually taught
The project was cancelled because a new CEO had different priorities. That sounds like bad luck. It was also structural.
A project that runs for six months without visible results is vulnerable. New management does not have to understand it and does not have to defend it. It can cancel it, and that is rational.
An incremental modernization approach would have delivered visible results after six months. Individual services rebuilt. Measurable performance improvements. Code already running in production and delivering value. That is harder to cancel because it is harder to ignore.
Full rewrites do not fail on technology alone. They fail because they promise too much for too long without delivering. And in a world where priorities shift, through new CEOs, through market changes, through budget pressure, that is a structural risk no technical design can solve.
The system it was supposed to replace is still running today. That is not tragic. It is instructive.

