Reducing technical debt without stopping feature work
Reducing technical debt works when you treat it like any other investment: measure where the interest is highest, rank debts by business impact, give repayment a fixed budget, and pay down in small changes that ship every week. It fails when it becomes a “cleanup quarter” or a rewrite nobody can finish.
This is the playbook I use when I’m embedded with a team whose delivery has slowed. It assumes you already know roughly what technical debt is and want to know what to do on Monday.
Step 1: Measure the interest, not the mess
The most common mistake in technical debt management is measuring code quality instead of cost. Linters and static analysis will flag thousands of issues, and most of them don’t matter. What matters is where the debt slows you down or breaks things. These are the measures I start with, all of which you can pull in a day:
- Lead time for changes: commit to production, per service or repo. A rising trend is the earliest signal that debt is compounding.
- Change failure rate: the share of deploys that cause an incident, rollback or hotfix. Group it by the area of code changed and the debt usually points at itself.
- Incident load: pages and incidents per week, tagged by component. If one module produces half your incidents, you have your first candidate.
- Hotspots: files that are both frequently changed and complex. Count commits per file over the last six to twelve months from git log, multiply by a complexity or size measure, and sort. Adam Tornhill’s work on behavioral code analysis popularized this, and it’s the single most useful technique I know for finding debt that charges interest.
- Unplanned work ratio: the share of each sprint spent on bugs, support escalations and firefighting rather than planned work.
- Engineer friction: ask the team which three areas they dread touching. They already know; the metrics confirm it.
Lead time and change failure rate are two of the five DORA metrics, which makes them easy to explain to a board and easy to benchmark against.
Step 2: Build a debt register
Turn what you found into a short, written list. Not a Jira epic with 300 tickets – a register of perhaps ten to twenty items that the engineering lead and product owner both read. For each debt, record:
- Where it lives (module, service, table).
- The interest it charges, in business terms: incidents per month, features delayed, onboarding time, deals lost to a missing capability.
- The rough principal: days or weeks to fix properly.
- The trigger: the event that makes paying it urgent (a customer size, a compliance requirement, a framework end-of-life date).
- An owner.
Step 3: Prioritize by business impact
Rank the register by interest divided by principal, adjusted for risk. In practice, the items that rise to the top fall into four groups, and I work them roughly in this order:
- Risk debt: anything that could cause a security incident, data loss or a billing error. Unsupported runtimes with known CVEs, missing backups you’ve never restored, non-idempotent payment webhooks. These go first regardless of ratio.
- Hotspot debt: the complex, high-churn files that sit on the path of the roadmap. Fixing these pays back within weeks because the team touches them constantly.
- Delivery debt: slow CI, flaky tests, manual deploys. Cheap to fix, and every other repayment goes faster once they’re gone.
- Strategic debt: structural changes the roadmap will need – tenancy isolation for enterprise customers, a data model that can support usage-based billing. Schedule these against the dates they’re needed.
What doesn’t make the list: code that’s ugly but stable, and modules nobody has touched in a year. Leave them alone.
Step 4: Budget it explicitly
Debt repayment that competes with features ticket by ticket will lose every time. Give it a standing budget instead. The common approaches:
- A fixed percentage of capacity – 15 to 25 percent of each sprint is typical. Simple, predictable, and the product owner can plan around it.
- Boy-scout rule with teeth: any feature that touches a hotspot includes the refactoring needed to make the change cleanly, and the estimate says so.
- Dedicated rotations: one engineer per sprint works the register. Good for small teams where a percentage gets lost in rounding.
- Time-boxed projects for strategic debt, with a defined outcome and an end date, planned like a feature.
I usually combine the first two. The percentage covers the register; the boy-scout rule stops new debt accumulating in the places that matter. Avoid the “stabilization quarter”. It signals that debt is something you deal with in bursts, and the pressure to resume features usually ends it halfway through.
Step 5: Pay down in small, shippable increments
Every repayment should be mergeable within a few days and deployable on its own. Large debt items get broken down the same way you’d break down a large feature. The techniques that make that possible:
- Characterization tests first. Before changing legacy code, write tests that pin its current behavior, bugs included. Then refactor under that safety net.
- Branch by abstraction. Introduce an interface in front of the thing you want to replace, move callers to it, swap the implementation behind it, delete the old one. Every step ships.
- Expand and contract for schema changes: add the new structure, dual-write, backfill, switch reads, remove the old. Covered in detail in zero downtime deployment.
- Strangle, don’t rewrite. For a whole subsystem, route traffic to new code piece by piece. See the strangler fig pattern.
- Feature flags to ship refactored paths dark, compare outputs, then cut over.
Step 6: Stop taking on reckless debt
Repayment is wasted if the same habits keep producing new debt. The cheapest technical debt reduction is the debt you never take on by accident. The controls that make the biggest difference are boring: code review with a clear standard, CI that blocks merges on failing tests, a definition of done that includes tests and migration scripts, and architectural decisions written down as short decision records so the next person knows which shortcuts were deliberate.
This matters more now that AI coding assistants generate a large share of new code. They produce plausible code quickly, which makes it easy to accumulate inadvertent debt at speed. Review standards have to keep up.
How to talk about technical debt cost with the business
Boards and founders don’t fund refactoring. They fund faster delivery, fewer outages and features that close deals. Frame each debt in those terms: “This module caused four of our last six incidents and adds roughly a week to any billing feature. Fixing it takes three weeks.” Then report progress in the same terms – lead time, change failure rate and incidents going down – rather than tickets closed.
Be honest when the answer is “leave it”. Some debt is cheaper to carry than to repay, especially in a product area you might retire.
When to bring in help
Most teams can run this playbook themselves. Outside help is worth it when nobody on the team has the time or authority to own the register, when the debt is structural (tenancy, data model, a framework several majors behind), or when the conversation has turned into a rewrite debate. That’s where a code audit or a hands-on legacy modernization engagement pays for itself – I work inside the codebase with the team, not from a slide deck.
Questions
How do you measure technical debt?
Measure its interest rather than its size. Track lead time for changes, change failure rate, incident load by component and the share of time spent on unplanned work, then find hotspots – files that are both complex and frequently changed – from git history. Static analysis scores alone overstate debt in code nobody touches.
How much time should we spend reducing technical debt?
A standing budget of 15 to 25 percent of engineering capacity is common and works well, combined with a rule that features touching a hotspot include the refactoring needed to change it cleanly. Fix the budget so repayment doesn’t compete with features ticket by ticket.
Should we pause features to fix technical debt?
Rarely. Feature freezes tend to end early under commercial pressure and leave work half-finished. Small, continuous repayment through a fixed budget is more reliable. The exception is risk debt – security, data loss or billing errors – which should be fixed immediately.
What is the cost of technical debt?
The cost shows up as slower delivery, more incidents, longer onboarding, lost deals when structural features take too long, and engineer attrition. Estimate it per debt in business terms – incidents per month, weeks added to features – so it can be weighed against the cost of fixing it.
Is a rewrite a good way to reduce technical debt?
Usually not. Big-bang rewrites take longer than planned, freeze the old system and recreate old bugs. Incremental approaches – refactoring hotspots, branch by abstraction and the strangler fig pattern – reduce debt while you keep shipping. A rewrite can be right in narrow cases, covered in my rewrite vs refactor guide.
Want the register built and the first debt paid?
In a two-week sprint I’ll find the hotspots, write the register with your team, and ship the first repayment. Then you decide whether to continue.
GMT+2 · Full EU overlap · US East mornings