Contents

Rewriting a working project from scratch makes sense only rarely: when the technology is dead, the architecture cannot meet new requirements and there is not much code. Otherwise, gradual refactoring and piece-by-piece replacement — the strangler fig pattern — is safer: new code grows around the old and gradually takes over its functions while the product keeps working and evolving.

Here is why rewrites often fail, when one is justified and how to replace a system gradually.

Why do rewrites from scratch so often fail?

A rewrite is tempting: the old code is tangled and the new code will be clean. But the old system holds years of fixes and business rules documented nowhere, and while the team rewrites, the product stands still or evolves in two places at once.

Typical reasons for failure:

  • hidden requirements — the old code handles dozens of edge cases nobody remembers;
  • a moving target — the old version keeps changing while the new one is written;
  • frozen development — the business gets no new features for months;
  • a big-bang cutover — everything switches at once and bugs hit every user;
  • underestimation — rewrites almost always take several times longer than planned.

When is a rewrite justified anyway?

A rewrite is justified when the project is small, the technology is no longer supported or cannot solve business problems, and gradual replacement would cost more. Decide after a technical audit, not on emotion.

Situation Leans refactor Leans rewrite
Code size Large, years of work Small, months of work
Technology Supported, can be upgraded Dead, no specialists or fixes
Business logic Complex, many edge cases Simple, well understood
Need to evolve New features constantly needed Can be frozen briefly
Tests Can be added Cannot be added

How to assess: software technical audit.

An excavator at a demolition site Photo: Unsplash

How does the strangler fig pattern work?

The pattern is named after the strangler fig, which wraps around a tree and gradually replaces it. A router — a proxy or API gateway — is placed in front of the old system; new and rewritten functions are built in the new system, and the router sends their requests there. The old system gradually loses functions and is finally switched off.

# The new system already serves catalogue and cart; the rest is legacy
location /api/catalog/ { proxy_pass http://new_app; }
location /api/cart/    { proxy_pass http://new_app; }
location /             { proxy_pass http://legacy_app; }

Steps:

  1. Put the router in front of the old system without changing behaviour.
  2. Pick the first module — small, valuable and loosely coupled.
  3. Build it in the new system and test it on part of the traffic.
  4. Switch the route, watch the metrics, delete the old implementation.
  5. Repeat until nothing of the old system remains.

Where do you start refactoring?

Start with tests that capture current behaviour: without them you cannot tell whether a change broke something. Then pick the places that change and break most often — improvements there have the biggest effect.

  • cover critical flows with end-to-end and integration tests — see automated testing strategy;
  • set up error monitoring to see the consequences of changes;
  • upgrade the language and framework to supported versions — see dependency updates;
  • improve code next to current tasks rather than in one huge separate project;
  • dedicate a steady share of team time to it.

How do you avoid creating new legacy?

Code becomes legacy not because of age but when people are afraid to change it: there are no tests, nobody understands how it works and the knowledge left with its authors. Preventing this is easier than fixing it.

  • tests for key flows and CI on every change;
  • regular dependency updates;
  • documented architecture decisions and APIs;
  • code review and shared standards;
  • explicit tracking of technical debt — see technical debt.

If you inherited the project from another vendor, start with how to switch development vendor.

In short

  • Full rewrites often fail because of hidden requirements and frozen development.
  • They are justified for small projects on dead technology.
  • The strangler fig pattern replaces a system piece by piece through a router.
  • Refactoring starts with tests and the places that change and break most.
  • To avoid new legacy, keep tests, updates, documentation and debt tracking.

FAQ

  • How long does a gradual replacement take?

    Usually from several months to a couple of years, but value arrives straight away: every migrated module runs on the new foundation. A rewrite delivers value only at the end.

  • Can the product keep evolving during refactoring?

    Yes, and that is the main advantage of the gradual approach. Usually part of the team's capacity goes to improving code, and new features are written straight into the new part of the system.

  • How do you explain the need for refactoring to the business?

    Through business metrics: how long a typical task takes, how many bugs follow releases, what downtime costs. Tie refactoring to concrete goals rather than code aesthetics.