Tackling old bugs without breaking what's still working.

A practical playbook for engineering leaders inheriting a 5-year-old codebase — how we triage, refactor, and ship without a quarterly fire drill.

Every engineering team eventually inherits the code someone wrote three jobs ago. The bugs are real, the documentation is partial, and the original author moved to a startup that since pivoted twice. Here’s how we approach it — both for our own legacy projects and the ones clients bring us to rescue.

1. Start with a real bug inventory, not a feeling

Before you touch a line, audit what’s actually broken. We use four buckets:

The mistake we see most often: jumping into the fourth bucket because it’s the most fun. Resist.

2. Build a safety net before you refactor

No refactor is safe without tests, period. If the code base has 10% coverage, your first sprint is dragging it to 40% on the modules you’re about to touch. We use a “write tests for the broken behavior first, then fix” pattern — it doubles as a regression suite and a specification.

3. Refactor in commit-sized slices

Big refactor PRs are dangerous because they’re hard to review and impossible to revert cleanly. We aim for refactor commits under ~200 lines each — small enough to read in 5 minutes, big enough to be meaningful. Each commit either keeps the tests green or fails one specific test for a documented reason.

4. Build for the next person, not the current crisis

When you fix a bug in legacy code, ask: would the next engineer find this fix obvious? If not, leave a comment — but a useful one (“this null check is here because the upstream API can return undefined for deleted records”) not a useless one (”// fix bug 1234”). Future-ready code is code that explains itself.

5. Know when to rewrite vs. refactor

The honest answer: rewrite is almost never the right call. Joel Spolsky’s “Things You Should Never Do, Part I” still holds up. But there’s one case where rewrite wins: when the original architecture makes the new requirements physically impossible. Then it’s not a rewrite — it’s a different product.

The playbook

Legacy code isn’t a problem to solve once — it’s a discipline to practice. Triage honestly, test before you touch, ship small commits, and write for the next person. That’s it. That’s the playbook.

Inheriting a legacy codebase and not sure where to start? We’ve rescued more than a few.

Let's talk