Technical Debt: How to Measure and Reduce It
Technical debt is inevitable. The problem is when it becomes invisible. Here is how to quantify it, prioritize it, and chip away at it without stopping feature delivery.
Technical debt is not always bad. Sometimes you take it on deliberately to ship faster and pay it back later. The problem is the unintentional, invisible kind that no one tracks and that quietly drags down every sprint. The goal is not zero debt, it is debt you can see, price, and decide about. This is how to measure it with real numbers, prioritise it, and reduce it without stopping delivery.
What is technical debt, exactly?
Ward Cunningham coined the term as a metaphor: shipping code is like taking a loan. It buys you speed now, and you pay interest, slower changes and more bugs, until you refactor. Martin Fowler split it into a useful quadrant: debt can be deliberate or inadvertent, and prudent or reckless. Deliberate and prudent, we know this is a shortcut and we will fix it after the launch, is a normal trade-off. Inadvertent and reckless, we did not know there was a better way, is the kind that hurts, and the kind measurement is meant to catch.
How to measure it
You cannot manage what you do not measure. Three layers work together: automated code metrics, a single ratio for stakeholders, and delivery signals at the team level.
| Signal | What it tells you | Tool | Watch threshold |
|---|---|---|---|
| Static analysis score | Bugs, code smells, complexity | SonarQube, PHPStan, ESLint | A rising trend, not the absolute value |
| Test coverage | Safety net for change | PHPUnit, Jest, coverage reports | Below ~60% on critical paths |
| Cyclomatic complexity | How tangled a function is | SonarQube, PHPStan | Above 10 per function |
| Code churn | Files rewritten again and again | git log, CodeScene | High churn plus high complexity |
| Change failure rate | How often a deploy breaks | DORA metrics, incident logs | Above 15% |
| Lead time for changes | Commit to production speed | DORA metrics | Trending up over time |
The Technical Debt Ratio
The one number a non-engineer can act on. It expresses debt as a percentage of the cost of the codebase itself:
Technical Debt Ratio (TDR) = (Remediation Cost / Development Cost) x 100
Remediation Cost = estimated effort to fix all known issues
Development Cost = estimated effort to rebuild the codebase from scratch
(commonly approximated as lines of code x cost per line)SonarQube computes this automatically with the SQALE method: every issue it detects is assigned a remediation time, those are summed, and the development cost is estimated at a configurable rate, 30 minutes per line of code by default. It then maps the ratio to a letter grade: A up to 5 percent, B up to 10, C up to 20, D up to 50, E above 50.
- ✓The trend matters more than the number: a stable 8 percent is healthier than a 4 percent climbing every month.
- ✓Compare modules, not whole projects: a greenfield service at 3 percent and a legacy core at 25 percent can both be normal.
- ✓It is an estimate, not an audit. Use it to start the conversation, not to end it.
Find the hotspots, not just the score
An aggregate score hides where the pain actually is. Cross two things: how often a file changes, its churn, and how complex it is. Files that both change constantly and are hard to reason about are where debt costs you real money, because you keep paying interest on them. Fix those first. CodeScene visualises this; a one-line git command gets you most of the way for free.
# Files changed most often in the last year
git log --since="1 year ago" --name-only --pretty=format: \
| sort | uniq -c | sort -rn | head -20Sort that list, then look at the top entries in your static analysis report. The overlap is your backlog.
Prioritise: the effort and impact matrix
Not all debt is worth fixing now. Score each item on two axes: business impact, does it touch critical paths or block work that is coming up, and fix effort. That gives four quadrants.
- ✓Low effort, high impact: do these first. They are the quick wins that build trust for the rest.
- ✓High effort, high impact: plan them as real work, sequenced against the roadmap, often behind a strangler pattern.
- ✓Low effort, low impact: fold them into the Boy Scout Rule below, no ticket needed.
- ✓High effort, low impact: document the risk and accept it. Not everything gets paid back.
Reduce it without stopping delivery
- ✓Boy Scout Rule: leave every file you touch a little cleaner. Rename a confusing variable, extract a duplicated block, add the missing test. It compounds.
- ✓Strangler pattern for large rewrites: replace the old code piece by piece behind a stable interface, shipping the whole time, instead of a big-bang rewrite that never lands.
- ✓Set a budget: a common figure is 15 to 20 percent of each sprint, sustained and visible. Protect it like any other commitment.
- ✓Tie every fix to a reason: a feature it unblocks, an incident it prevents, a cost it removes. Debt work with no stated why is the first thing cut.
- ✓Add a rule to your definition of done: new debt is allowed, but only with a ticket that names it.
You rarely need a dedicated refactoring sprint. Dedicated sprints slip, get cancelled when a deadline moves, and let debt build up between them. Continuous small improvement, protected as part of normal work, beats it almost every time.
Make it visible to stakeholders
Stakeholders fund outcomes, not refactoring. Translate every debt item into their language: this cuts the release cycle by two days, this removes the class of incident that caused last month's outage, this is what is blocking the payments feature. Then put debt items in the same backlog as features, estimated the same way, so the trade-off is explicit rather than hidden.
- ✓Report a debt line in every sprint review, next to the feature summary.
- ✓Track the TDR grade over time and show the graph, not just the current letter.
- ✓Set a shipping policy: no module goes to production below an agreed coverage or grade.
Common mistakes
- ✓Chasing a perfect score instead of watching the trend.
- ✓Big-bang rewrites that consume months and never ship.
- ✓Treating all debt as equal instead of prioritising by impact.
- ✓Hiding debt work as miscellaneous instead of naming it, which makes it invisible again.
- ✓Adding a static analysis tool and never acting on its output.
FAQ
How do you calculate technical debt?
The standard metric is the Technical Debt Ratio: the estimated cost to fix all known issues divided by the estimated cost to rebuild the codebase, expressed as a percentage. SonarQube calculates it automatically by assigning a remediation time to each detected issue and dividing by an estimated development cost. Below 5 percent is considered healthy.
What is a good technical debt ratio?
Under 5 percent is generally healthy, which SonarQube grades as A. Five to ten percent is manageable. Above 20 percent, change is getting expensive and the trend needs to reverse. Context matters: a legacy system at 25 percent that is stable and trending down can be perfectly acceptable.
How much time should you spend on technical debt?
A common budget is 15 to 20 percent of each sprint, sustained. That is usually enough to keep the trend flat or downward without derailing the roadmap. One-off debt sprints tend to be the first thing cut when a deadline moves.
Is technical debt always bad?
No. Deliberate, documented debt taken to hit a deadline or validate an idea is a legitimate trade-off. The dangerous kinds are inadvertent debt that no one tracks and prudent debt that is never actually paid back.
Debt you can see and price is a manageable line item. Debt you cannot is what quietly kills velocity. Measure the ratio, watch the trend, fix the hotspots first, and keep the conversation with the business in the language of delivery.
Need help with this topic? Technical Audit
Discover this service →