Technical Debt: How to Measure It and Actually Pay It Down#
Every engineering team says it has too much technical debt. Very few can say how much, where it is, or what it costs. Without that, debt reduction becomes a vague request for "a refactoring sprint" that product managers reasonably decline.
Treating technical debt as a business problem — with evidence — is what finally gets it addressed.
Not all debt is bad#
Ward Cunningham's original metaphor was about deliberately shipping faster now and paying back later, like a loan. That is often the right call: validating an idea quickly beats building a perfect system nobody wants.
A useful way to categorize it:
- Deliberate and prudent: "We'll ship with a simple implementation and revisit after launch." Fine, if you actually revisit it.
- Deliberate and reckless: "We don't have time for tests." Usually regretted.
- Inadvertent: "Now we know how this should have been designed." Inevitable as teams learn.
- Bit rot: code that was fine when written but decayed as dependencies, requirements and people changed.
The goal is not zero debt. It is debt you chose consciously, with interest you can afford.
The interest is what matters#
Debt in code nobody touches costs almost nothing. Debt in a module that changes every week costs you on every change. So the question is not "how messy is this code?" but "how much does this mess slow us down?"
Signals that debt is charging real interest:
- Small features in an area consistently take far longer than estimated.
- The same area produces a disproportionate share of bugs and incidents.
- Engineers avoid working in certain modules, or only one person can.
- Onboarding a new engineer to an area takes weeks.
Measure it with data you already have#
You do not need special tools to start. Combine two sources:
Change frequency from git history — which files change most often:
1git log --since="6 months ago" --name-only --pretty=format: \2 | grep -v '^$' | sort | uniq -c | sort -rn | head -20
Pain from your issue tracker and incident log — which areas generate bugs, incidents and slow tickets.
Files that are both frequently changed and frequently painful are your hotspots. In my experience, a small percentage of the codebase usually accounts for most of the drag. That is where repayment pays off.
Make the case in business terms#
"The billing module is a mess" gets a shrug. This gets a decision:
Over the last two quarters, billing changes took on average 2.6× their estimate and caused 7 of our 15 production incidents. Refactoring the payout calculation and adding integration tests is about three weeks of work. We expect billing features to ship roughly twice as fast afterwards, and fewer payment incidents.
Tie the work to outcomes the business cares about: delivery speed, reliability, security risk, ability to hire and onboard.
Strategies for paying it down#
The boy scout rule#
Leave code slightly better than you found it. Rename a confusing variable, extract a function, add a missing test while you are already working in the area. It costs little, and it targets the most-touched code automatically.
Continuous allocation#
Reserve a steady share of capacity — many teams use 15 to 20 percent — for debt and maintenance. Steady investment beats rare, large "cleanup sprints" that get cancelled when deadlines arrive.
Refactor as part of features#
The easiest debt to fund is debt in the way of a planned feature. Estimate "refactor the notification module, then add SMS support" as one piece of work. The business gets its feature; the codebase gets better where it mattered.
Strangle, do not rewrite#
Big rewrites are among the riskiest projects in software. They take longer than planned, the old system keeps changing underneath, and they rarely reach feature parity. Prefer incremental replacement: route one piece of functionality at a time to new code until the old path can be deleted.
Prevent new reckless debt#
- Automate the basics: linting, formatting, type checking and tests in CI.
- Keep a lightweight decision log, so future engineers understand why shortcuts were taken.
- When you knowingly take on debt, create the ticket immediately, with context and a trigger for revisiting it.
- Make code review a place to discuss design, not just style.
Conclusion#
Technical debt is a normal part of building software. Manage it like a financial portfolio: take on debt deliberately, focus repayment on hotspots where the interest is highest, measure with git history and incident data, explain the cost in business terms, and fund repayment continuously rather than in rare heroic sprints.