Pay down technical debt when the affected code changes often, is measurably slowing delivery or causing incidents, or when the people who understood the original shortcut are gone. Leave it alone when none of those are true — a rarely touched module that still works and is still understood by someone on the team costs more to fix than to ignore. The mistake most teams make isn't taking on debt in the first place. It's grading every piece of debt the same way instead of pricing each one on its own.
Debt isn't one number
"Technical debt" gets used to describe everything from a missing test suite to a database schema nobody wants to touch to a three-year-old auth system held together with special cases. Lumping all of it into one backlog item — "pay down tech debt" — is why debt conversations go nowhere. Some of that debt is actively expensive. Some of it isn't costing you anything at all.
Debt only functions like real financial debt when it compounds: when working around it, or building on top of it, gets more expensive every time someone has to touch that code. A shortcut in a file that was written once and hasn't been opened since isn't compounding. It's a decision that already happened and stopped mattering.
The three questions that actually decide it
Before scheduling a refactor, run the debt through three checks. Debt that fails most of them isn't urgent, no matter how ugly the code looks.
- How often does this code path change? Debt in a module that gets touched every sprint compounds every sprint — each new feature has to work around the same shortcut, and the workaround gets more elaborate each time. Debt in code that hasn't been opened in eight months isn't accruing interest. It's just sitting there.
- Is it actively slowing delivery or causing incidents? Look at your own data before guessing: how many recent tickets or postmortems trace back to this area, and how much longer do estimates run when a task touches it. If engineers routinely pad estimates for a module or if it shows up repeatedly in incident reviews, that's a cost you can point to. If it's just code you'd write differently today, that's taste, not cost.
- Has the team turned over since it was written? Debt that's undocumented and was taken on by someone still on the team is manageable — they know which parts are safe to touch and which aren't. The same debt after two rounds of turnover is a different risk entirely: nobody left can tell you why a check was skipped or what the edge case was protecting against, and every change against it is a guess.
A piece of debt that scores high on all three — changed often, provably slowing things down, and orphaned by turnover — belongs at the top of the backlog. A piece that scores low on all three should stay exactly where it is.
Two ways to get this wrong
Teams tend to land on one of two policies, and both are expensive in different ways.
Never paying anything down. Every shortcut stays in place because there's always a feature that feels more urgent. The problem is that debt in actively developed code doesn't stay flat — each new feature built on top of a bad abstraction makes the next one harder to build, and estimates quietly inflate over several release cycles until someone finally proposes a full rewrite. A rewrite is the most expensive way to resolve debt: it stalls new feature work for months and reintroduces bugs the original code had already worked out. Paying down debt in small pieces while it's cheap is what avoids being forced into that position.
Refactoring everything on a schedule. The overcorrection is a blanket policy — "20% of every sprint goes to tech debt," applied uniformly with no distinction between code that's costing the business money and code that's merely unfashionable. This burns budget on modules nobody is going to touch again and starves the one system that's actually generating incidents. A refactor policy without a filter is just a different way of ignoring the data.
Two shortcuts, two different outcomes
A client of ours had a CSV export feature for monthly reports, built quickly by loading records into memory instead of streaming them. It's inefficient by any code review standard. But the export runs for one internal team, twice a month, against a dataset that hasn't grown much in three years, and the engineer who wrote it is still on staff. It fails all three tests for urgency. We left it alone, and it's still running unchanged today.
The debt that should have been fixed sooner was in the same client's permissions layer — a set of manually maintained role checks bolted onto the core application early on to hit a launch date. That code sat in the path of nearly every new feature, so each addition meant another special case. Two of the three original engineers had left within eighteen months, and a permissions gap surfaced in production before anyone flagged the module as a risk. By the time we rebuilt it around a proper role-based access model, the fix took longer than it would have a year earlier, because there was more code depending on the old logic by then.
Make the call per module, not per company
Technical debt isn't a moral failing and it isn't free to carry either — it's a cost that has to be priced against the code it actually sits in. Run the three questions against a specific piece of debt, not against your codebase in general, and schedule the fix based on what the data says, not on how the code makes you feel when you scroll past it.

