The deadline is in two weeks. The feature is scoped. The designs are approved. Everything's ready — except the code you need to build on top of is held together with duct tape and hope.
This is one of the most common situations we walk into. A team under delivery pressure, staring at a section of the codebase that needs serious work, but with no time allocated for "refactoring." The feature was estimated assuming the foundation was solid. It isn't.
The two bad options
Most teams see two choices:
Option A: Rewrite first, then build. The responsible choice — in theory. In practice, rewrites take longer than expected, scope creeps, and the deadline blows past. Stakeholders lose trust. The team gets burned and becomes even more reluctant to propose refactoring next time.
Option B: Build on top of the mess. Ship it, hit the deadline, deal with the consequences later. Except "later" never comes. The mess gets worse. The next feature on top of this one takes even longer. Technical debt compounds with interest.
Both options are bad because they treat refactoring and delivery as opposing forces. They're not.
The third option: refactor along the path
Here's the approach we use — and it works every time.
Don't refactor the whole module. Refactor only the surface area your feature needs to touch.
This means:
- Map the feature's dependency path. Trace exactly which functions, modules, and data flows your new feature will interact with. Ignore everything else.
- Write characterization tests for that path. Not the whole module — just the parts you're about to change. This gives you a safety net without the overhead of testing everything.
- Refactor the path. Clean up the code your feature needs. Extract interfaces. Clarify naming. Untangle the specific dependencies that are in your way.
- Build the feature on the clean path. Now you're building on solid ground, and the new code is clean from day one.
The module isn't fully refactored. But the part that matters is. And every future feature that touches the same path benefits from the work you just did.
Why this works under pressure
The key insight is scope control. A full rewrite has unbounded scope — you don't know what you'll find until you're deep in it. But refactoring along the feature path has a natural boundary: the feature itself defines what needs to change.
This means:
- Estimates stay accurate — you're refactoring a known, bounded surface area.
- Progress is visible — the feature is advancing at the same time as the cleanup.
- Risk is contained — you're not rewriting code that doesn't need to change right now.
- Quality improves incrementally — each feature delivery leaves the codebase a little better.
The compounding effect
When you do this consistently — refactor along the path with every feature — something interesting happens. Over the course of several sprints, the most-trafficked parts of the codebase get cleaner organically. The paths that matter most get the most attention. The code that's rarely touched stays messy, but that's fine — it's not in anyone's way.
It's not a grand rewrite. It's disciplined, incremental improvement driven by actual delivery needs. And it doesn't require anyone to approve a "refactoring sprint" that never seems to get prioritized.
When this isn't enough
Sometimes the code is so far gone that even the feature path can't be safely cleaned up in the time available. The dependencies are circular, the state management is global, and every function has thirty side effects.
That's when you bring in outside help. Not to do the full rewrite — but to do the surgical refactoring that the team doesn't have the bandwidth or the familiarity to do safely under pressure. A fresh pair of eyes with experience in exactly this kind of work can move through tangled code faster than a team that's been working around it for years.
The next time someone says "we don't have time to refactor," remember: you don't have to refactor everything. You just have to refactor the path you're walking. That's always possible, and it always pays off.