in

Technical Debt: What It Is, How to Measure and Pay It Down

The bill for 'we'll fix it later' arrives six months on as lead times that have doubled and sprints consumed by unplanned work. What technical debt is and isn't, its types, how to measure it with signals instead of a single number (lead time, unplanned-work share, hotspot analysis, dependency age), the debt register, prioritizing by interest and principal, repayment strategies and how to explain it to management.

Chart of technical-debt interest lengthening lead times over time, with a debt register table

"Let's do it quickly now and fix it later." This sentence is spoken in every software project, and it is often the right decision: the deadline is real, the perfect design isn't. The problem is that "later" never comes and the decision accrues interest like a debt: six months on, a three-day change takes two weeks, half the sprint goes to unplanned work, and when the senior developer leaves nobody can touch that module. Management asks "why did we slow down"; the team says "technical debt"; nobody can give a number.

This article covers what technical debt is and isn't, its types, how to measure it with signals instead of a single number, the debt register and prioritization by interest and principal, repayment strategies, the guardrails that prevent new debt and how to explain debt to management. It's written for both team leads and the managers who approve the budget.

What technical debt is, and what it isn't

Ward Cunningham's metaphor: a design shortcut taken to ship faster today is a debt; until it's repaid, interest (extra effort) is paid on every change. The critical distinction: code that accrues no interest isn't debt. An ugly module that hasn't changed in ten years, works and is never touched is history, not debt. Debt is the shortcut in places that change often or produce incidents. Martin Fowler's quadrant separates the quality of the decision:

Deliberate

Inadvertent

Prudent

"We must ship now, we know the consequences, we recorded it" — healthy debt

"Now we know how we should have done it" — the natural debt of learning

Reckless

"We don't have time for design" — debt taken knowingly and never recorded

"What's layering?" — debt from lack of knowledge

A good team doesn't zero its debt; it keeps it prudent and recorded, and reduces the reckless kind.

Types

Type

Example

How the interest shows

Code debt

Copy-pasted business rules, an 800-line service, magic numbers

One rule change means fixes in five places; regressions

Architecture debt

No module boundaries, a shared database, circular dependencies

Small change, wide impact; teams wait for each other

Test debt

No coverage, flaky/slow tests, manual testing

Fear on every release; long stabilization periods

Dependency/infrastructure debt

Java 8, an unsupported framework, libraries never updated

Vulnerabilities, new libraries won't install, hiring gets harder

Knowledge/documentation debt

A module only one person knows, no decision records

A standstill when the person leaves; the same debates repeated

Security debt

Postponed patches, missing authorization checks

Audit findings, incident risk

Data debt

Meaningless columns, inconsistent codings, unvalidated input

Reports don't reconcile; migration projects blow up

How to measure it: signals, not a number

The "technical debt: 412 days" figure from static-analysis tools is attractive but misleading: it counts code smells, it doesn't measure interest. The real measure of debt is how much more expensive change and operations have become. A set of signals read together:

Signal

What it says

How it's collected

Lead-time trend (idea → production)

The total interest of the debt

The work-tracking tool; monthly median and p85

Unplanned-work share

How much sprint capacity goes to firefighting

Work labels (bug, incident, urgent); target <20%

Change failure rate

How many releases need a rollback/hotfix

Deployment records (DORA)

Hotspot analysis (change frequency × complexity)

Where the interest is paid

Git history + complexity; the command below

Dependency age and EOL count

The size of infrastructure debt and the security risk

Dependency scanning; the number of "end of life" items

Test coverage on changed code, build time, flaky-test count

Test debt

CI reports; flaky-test rerun counts

Incident tags

Which debt produces incidents

A root-cause tag on every incident (module, type)

Developer friction survey

Interest that isn't measurable but is felt

Five questions quarterly: "What wastes the most time?"

# Hotspots: the files changed most often in the last 12 months (change frequency); multiplied by complexity (lines, cyclomatic complexity)
git log --since="12 months ago" --format=format: --name-only -- src/main | grep . | sort | uniq -c | sort -rn | head -20

# Example output: 143 src/main/java/com/acme/order/OrderService.java  ← changes often + 1,900 lines = priority one
#                 97  src/main/java/com/acme/pricing/PriceCalculator.java

Hotspot analysis is the most valuable measurement because it shows not all the debt but the part that accrues interest: 4% of the codebase typically carries 60% of the changes. That's where the repayment effort goes.

The debt register and prioritization

Debt is managed item by item, not as an anonymous "the architecture is bad" complaint. Every item: location (module/file), type, interest (hours lost per month or incident count — an estimate is enough), principal (repayment effort in days), risk (security, EOL date), owner, decision (repay / watch / accept). It lives in the work-tracking tool; architectural items link to an ADR (architecture decision record).

Item

Interest (month)

Principal (days)

Interest/principal

Decision

OrderService (1,900 lines, hotspot #1)

~40 hours

15

High

Repay: 2 sprints, alongside feature work

Java 8 + Spring Boot 2.x

~10 hours + security risk

25

Medium, high risk

Repay: this quarter (EOL)

Reporting module (never changes, ugly)

~0

20

Zero

Accept; don't touch

Manual regression testing (3 days/release)

~24 hours

30

Medium

Repay: gradual automation

The prioritization rule: high interest/principal ratio and in a hotspot first; EOL and security items by date; an item with zero interest, however ugly, sits at the bottom of the list.

How to pay it down

  1. A continuous budget: 15–20% of capacity in every sprint for debt repayment; a continuous flow instead of waiting for a separate "debt sprint". Make the budget visible: on the dashboard and in the sprint goal.

  2. Clean what you're about to change: Repayment focuses on hotspots and the code the next feature will touch; the "leave it a little better than you found it" rule is embedded in feature work. Rewriting code that doesn't change is waste.

  3. Tests first, then refactor: Refactoring without tests isn't repaying debt, it's borrowing more; characterization tests are the first step.

  4. Projects for big items: Framework migrations, module extraction, database splits are time-boxed, measured and done strangler-fig style; not big bang.

  5. Dependency automation: Small, frequent updates with Renovate/Dependabot; major upgrades planned. The dependency-age metric drops, and the EOL crisis never happens again.

  6. For knowledge debt: Pairing, module-ownership rotation, ADRs; the list of single-person modules is tracked as a management risk.

Guardrails that prevent new debt

// ArchUnit: architecture rules as tests — a violation breaks the PR, and the debate happens now, not later
@AnalyzeClasses(packages = "com.acme")
class ArchitectureRulesTest {
    @ArchTest
    static final ArchRule domain_must_not_depend_on_infrastructure =
        noClasses().that().resideInAPackage("..domain..")
            .should().dependOnClassesThat().resideInAnyPackage("..adapter..", "org.springframework..", "jakarta.persistence..");

    @ArchTest
    static final ArchRule no_cycles_between_modules =
        slices().matching("com.acme.(*)..").should().beFreeOfCycles();

    @ArchTest
    static final ArchRule controllers_must_not_call_repositories =
        noClasses().that().resideInAPackage("..api..").should().dependOnClassesThat().resideInAPackage("..repository..");
}

More guardrails: a CI quality gate on new and changed code only (the "no worse from today" principle instead of cleaning old code overnight); tests in the definition of done; deliberate debt decisions tagged in the PR with // DEBT: … (DEBT-142) and in the register; architectural decisions recorded as ADRs.

How to explain it to management

Management doesn't understand code smells; it understands three things: money, time, risk. Translate the interest into that language: "In a ten-person team, 25% of capacity goes to unplanned work and slowness; that's two and a half salaries" — then a measurable promise such as "if we repay these three items this quarter, unplanned work drops from 25% to 12%". Explain risk with dates: "The framework's security support ends in March; it becomes an audit finding." Pick three items per quarter, show the trend chart (lead time, unplanned-work share), report the difference after repayment. Trust comes after the first measurable win; a "let's rewrite everything" proposal ends it.

Common mistakes in the field

  1. Anchoring on a single number: If the static-analysis "412 days" is falling while lead time is rising, the wrong thing is being measured.

  2. The big cleanup sprint: A three-week "refactoring sprint" usually stops halfway and brings regressions; a continuous budget works better.

  3. An ownerless register: The list grows, nobody repays.

  4. Refactoring without tests: An incident instead of a repayment.

  5. Beautifying code that never changes: Paying principal on a zero-interest item.

  6. Postponing dependency debt until EOL day: A planned quarter of work becomes an unplanned crisis.

  7. Making debt a moral issue: "What's the interest, what's the principal" instead of "who wrote this".

Frequently asked questions

When do you declare "bankruptcy" (a rewrite)?

When the interest is so high that paying the principal is impossible and the platform is truly dead; but that's rare, and a rewrite produces its own debt. In most cases the right answer is gradual repayment through strangler fig (see modernization).

Do AI coding assistants increase or reduce debt?

Both are possible: without rules, quickly generated and poorly understood code increases debt; used correctly for test writing, explanation and mechanical refactoring, they speed up repayment. The deciding factor is review discipline (see AI coding assistants).

How do you prevent debt in a new project?

You don't prevent it, you manage it: a quality gate, ArchUnit rules, ADRs and a debt tag from day one; every deliberate shortcut goes into the register. A zero-debt goal means never shipping.

Conclusion

Technical debt is inevitable and, managed well, a tool; unmeasured and ownerless, it's an interest burden that slowly brings the team to a halt. The right approach: find where interest is accruing with hotspots and delivery metrics, record it item by item, prioritize by interest and principal, repay with a continuous budget starting from the code you're about to change, limit new debt with guardrails and explain it to management in the language of money, time and risk.

To build your team's technical-debt register, run a hotspot analysis and set up a measurable repayment plan, see our software design and architecture and maintenance and support services.