Chat on WhatsApp
All articles

Taking Over a Legacy Codebase Without Rewriting It

September 1, 2026 · 8 min read · Belal Nagy

Legacy CodeReact NativeRefactoring

Medicta came to me as a live React Native app on Google Play: real users booking doctor appointments and home visits, a bug list long enough to hurt, an outdated UI, and no access to the developers who wrote it. The client had already heard the advice every developer gives in that situation — "this needs a rewrite" — and could not afford six months of paying for a product they already owned.

I stabilized it, redesigned the UI screen by screen, and shipped it back to Google Play without ever stopping the app that users depended on. This is the playbook, because takeovers are a discipline of their own, and almost everything about them runs against a developer's instincts.

First, earn a reproducible build

Nothing else matters until the code on your machine provably becomes the app in the store. Legacy mobile projects fail here constantly: undocumented signing keys, dependency versions that no longer resolve, native modules pinned to an SDK three versions back, an .env file that lives only on a former developer's laptop.

The first deliverable was not a fix — it was a build: check out, install, compile, run on a device, and a written record of every step and every credential needed to do it again. It is unglamorous work the client never sees, but it converts the project from "a zip file we are afraid of" into software. Only then did I touch a bug.

Read before you judge

Every takeover begins with the urge to condemn the previous developer. Resist it — not out of politeness, but because it produces bad engineering. Code that looks insane is usually load-bearing in a way you have not discovered yet: the weird retry wrapper exists because the booking API double-charges on timeout; the hand-rolled date parser exists because the backend sends three different formats.

I spent the first days mapping instead of fixing: which screens exist, which API endpoints they call, where state lives, what the crash reports actually say. The map does not need to be complete — it needs to be honest about which parts are understood and which are still dark. Fixes stay inside the lit area; the dark area shrinks one bug at a time.

Triage like an ER, not like a backlog

A legacy bug list is not a backlog to burn down in order. I sorted Medicta's issues into three bands: bugs that lose money or users right now (booking failures, crashes on launch), bugs that erode trust slowly (wrong labels, stale data, broken edge cases), and everything cosmetic. The first band ships as fast as it safely can; the second is scheduled; the third waits for the redesign to absorb it for free.

The band system also gave the client something rewrites never do: visible progress in week one. Users felt the app get better while the deeper work was still underway — and a client who sees motion extends trust, which a takeover consumes quickly otherwise.

Change in the smallest possible units

In a codebase with no tests and no original authors, the size of a change is the size of its risk. Every fix was the smallest diff that resolved the issue — no drive-by cleanups, no "while I'm here" refactors mixed into bug fixes. When a refactor was genuinely needed, it shipped alone, so that if something regressed, the cause was one commit and not an archaeology project.

The UI redesign followed the same rule at a larger scale: screen by screen, not app in one leap. Each release replaced a handful of screens with the new design while the rest kept working as before. Users got a steadily improving app instead of a big-bang update, and every release stayed small enough to roll back.

The rewrite question, answered honestly

Rewrites are sometimes right — when the platform is dead, the framework unsupported, or the architecture actively blocks the business. None of that was true here. What the rewrite instinct usually expresses is that reading other people's code is harder than writing your own. That is true, and it is also the job.

The takeover cost a fraction of a rewrite, kept revenue flowing the entire time, and preserved years of accumulated edge-case knowledge encoded in that "ugly" code — knowledge a rewrite would have had to rediscover one production incident at a time.

The takeaway

If you inherit a live codebase: make the build reproducible before anything else, map before you fix, triage by business damage rather than by ticket order, keep every change as small as its risk demands, and treat the rewrite as a last resort with a business case — not a first instinct with an aesthetic one. The client does not need beautiful code. They need the app they already paid for to work, and to keep working while it gets better.

Next article

Arabic RTL That Survives Dashboards and Invoices