Skip to content
Published on

The Complete Guide to Technical Debt: Identify, Measure, Repay

Share
Authors

Introduction

This blog already has Technical Debt in Business Language. That post covers how to explain debt to stakeholders and negotiate its priority — it is an article about persuasion.

This post is the step before that. To have something to persuade anyone about, you first have to find where the debt is and attach a number to it. "The code is messy" with no evidence behind it never wins at the negotiating table. So the subject here is identification, measurement, and the procedure for maintaining a list.

Reduced to one sentence: claiming debt exists and showing what it costs per month are different acts, and only the second one moves a budget.


1. What the metaphor originally meant, and how it gets misused

1-1. Fowler's quadrant

Martin Fowler splits technical debt along two axes: was it deliberate or inadvertent, and was it reckless or prudent?

                  Reckless                        Prudent
Deliberate    "We don't have time for design"  "Ship now, repay later"
                  skipped without knowing           chose it knowing the cost
                  the price

Inadvertent   "What's layering?"               "Now we know how we should
                  did not know better               have done it"
                                                    learned it by building it

Fowler emphasises the inadvertent-prudent quadrant in particular: it can take a year of programming on a project before you understand what the best design approach should have been. And his point is that the metaphor is useful across all four quadrants, not just the deliberate ones.

1-2. What the distinction does in practice

The quadrant is not a classification game; it matters because the prescription differs.

  • Deliberate and prudent: fix the repayment schedule and conditions on the spot. Skip that and it looks like inadvertent debt next quarter.
  • Deliberate and reckless: a process problem. Instead of blaming a person, look at why that choice was possible — deadlines and review standards.
  • Inadvertent and reckless: a learning and review problem. When the same mistake recurs it is onboarding and guidance that are missing, not competence.
  • Inadvertent and prudent: normal. A team ashamed of this stops feeding what it learns back into the code.

1-3. This is contested

Whether the "technical debt" metaphor is useful at all is a genuine argument. The objection is that the phrase launders bad engineering in financial vocabulary. Debt presupposes a deliberate choice and a repayment plan, yet in practice the same word gets attached to bad code produced with no plan whatsoever. The defence is that it is close to the only shared language available for talking with non-technical stakeholders.

Three axes: whether there really was intent, whether a repayment plan exists in writing, and what behaviour the word induces in the listener. Used without a plan, the objection is right; used with one, the defence is.


2. Things that are not debt

Filter before you build the list. There is one test: does it charge extra for future changes? If it does not, it is not debt.

  • Differences of taste: code written in a different style is not debt. Put style arguments on the debt list and the whole list loses credibility.
  • Old code that does not change: a module nobody has touched in three years that works correctly earns zero interest, and therefore has zero priority.
  • Abstraction you do not need yet: not generalising is a judgement, not a debt. Duplication before the third instance is usually fine.
  • Bugs: defects, not debt. Defects get fixed; debt gets repaid or explicitly not repaid.
  • Unfinished features: that is the backlog.
  • Deliberately simple design: lacking extensibility and having decided to forgo extensibility are different things.

Conversely there are things rarely recognised as debt that are: slow CI, manual pre-deployment checks, environments that do not reproduce locally, and a state where a new hire needs two weeks to land a first commit. None of them show up in the code, and all of them charge interest on every change.


3. A typology of debt

The point of typing debt is that discovery and repayment differ by type.

TypeWhere the interest shows upHow to find it
CodeTime per change, repeated review commentsHotspot analysis, review comment counts
Design/architectureOne feature touches many modules at onceChange coupling, files that change together
TestCI time, flaky failures, fear of deployingFailure rate, duration, coverage gaps
Dependency/securityCannot upgrade, spec violations still presentVulnerability scans, end-of-support dates
Data/schemaMigration risk, deployments you cannot reverseUn-contracted columns, leftover dual writes
OperationsIncident frequency, recovery time, manual stepsIncident retros, manual steps in runbooks
Knowledge/docsOnboarding time, the same question repeatedDays to onboard, question logs

3-1. Debt with an external clock

We usually choose when to repay, but some items have their deadline set from outside. Mark these separately on the list.

  • Changes in security specifications: RFC 9700, the OAuth 2.0 security best current practice, states that the resource owner password credentials grant "MUST NOT be used", and that clients "SHOULD NOT" use the implicit grant unless access token injection in the authorization response is prevented. Items like these are bound to audit and review calendars, not to ours.
  • Storage location problems: the OWASP Session Management Cheat Sheet says not to store authentication tokens, session IDs, JWTs or refresh tokens in localStorage or sessionStorage, because those APIs are accessible to any JavaScript executing in the origin, so a single XSS vulnerability discloses every token. The trade-offs are laid out in JWT vs. Sessions: Which, and When.

3-2. Operational debt only bills you during an incident

The Google SRE Book defines a cascading failure as a failure that grows over time as a result of positive feedback, and recommends that you always use randomised exponential backoff when scheduling retries. Its example retry budget is 60 retries per minute in a process. It also points out that retries multiply across layers: three layers retrying four times each turn one user action into 64 attempts.

Having no backoff, no budget and no load shedding costs nothing on an ordinary day. Because the interest is billed all at once during an incident, the item keeps losing on priority. For this type, write the interest as "probability of occurrence times cost per incident" or it will not survive on the list.

3-3. Knowledge debt

The Design Docs at Google article notes that design docs, like all documentation, tend to get out of sync with reality over time. The response is to update the original, amend it, or link the follow-ups. More dangerous than a stale document is a state where nobody knows whether it is stale.


4. Finding where it is — change frequency and complexity

4-1. Look for expensive code, not bad code

The most common mistake in debt discovery is starting from the messiest file. A messy file nobody touches earns zero interest. What you want is code that changes often and is hard to change.

# Example — pulling the most frequently changed files out of version control history
# Last 12 months, top 30 files by commit count
git log --since='12 months ago' --name-only --pretty=format: \
  | grep -v '^$' | sort | uniq -c | sort -rn | head -30

# Multiply this list by complexity or line count to get a hotspot score
# score = change count x complexity metric
# The top 10 are this quarter's candidates

4-2. Traps in the calculation

  • Generated files and lockfiles: overwhelming commit counts that no human reads. Exclude them first.
  • Mass formatting commits: one style application raises every file's change count equally. Exclude that commit.
  • File renames: history breaks, and an old hotspot looks like a new file.
  • Size bias: large files simply change more often. Also look at change count divided by line count.

4-3. Signals outside the code

Hotspot analysis only sees inside the code. These signals nominate candidates from outside it.

  • Repeated incidents in the same file: count file names across retro documents.
  • Repeated review comments: the same comment appearing three times is a structural problem, not a personal one.
  • Repeated onboarding questions: the most honest indicator of knowledge debt.
  • The number of manual pre-deployment steps: count the steps a human performs in the runbook.
  • "Don't touch that one": any module that sentence attaches to is a candidate, without exception.

4-4. Do static-analysis debt scores mean anything — this is contested

Whether you can trust a tool's debt score or its "time to remediate" estimate is disputed. The useful camp argues that it is good enough for tracking a trend and gives the discussion a starting point. The meaningless camp argues that the number is just a count of rule violations times arbitrary weights, and that it cannot see design coupling or operational debt — the two most expensive kinds.

Two axes: whether you use the score as an absolute or as a trend, and whether it correlates with your actual change costs. If the score and change lead time do not move together in your repository, that score means nothing for you.


5. How to calculate the interest

5-1. The interest matters, not the principal

An item that says only "three weeks to fix" states the principal. Principal alone cannot prioritise anything. What you need is what it costs every month while unpaid — the interest.

[Estimating interest — write only observable quantities]
interest(month) = (extra time this debt adds to one change)
                x (number of changes in that area per month)
                + (monthly outage hours caused by it x cost of one outage hour)

Examples
- Dual schema in the payment module: +4h per change, 6 changes/month → 24h/month
- Flaky E2E tests: 1.5h waiting on re-runs x 5 times/week → 30h/month
- Manual pre-deploy checks: 40 min per deploy x 20 deploys/month → 13h/month

Write the principal (repayment estimate) as a range, e.g. 10-15 person-days
Payback period = principal / interest. For the payment module above, about 4-6 months

5-2. Using DORA metrics as a proxy for interest

The easiest way to observe interest at team level is through deployment metrics. DORA defines change lead time as the amount of time it takes for a change to go from committed to version control to deployed in production, change fail rate as the ratio of deployments that require immediate intervention following a deployment, and failed deployment recovery time as the time it takes to recover from a deployment that fails and requires immediate intervention. Rework rate is the ratio of unplanned deployments caused by a production incident.

When debt accumulates these metrics move first. Change fail rate and recovery time in particular respond directly to test debt and operational debt.

5-3. Why "repaying debt slows us down" is the wrong frame

DORA reports that its research has repeatedly demonstrated that speed and stability are not trade-offs, and that for most teams the metrics are correlated. In DORA's words, the real trade-off over long periods of time is between better software faster and worse software slower.

That result directly refutes the "pick either quality or speed" frame that dominates debt conversations, and it is the strongest single piece of evidence available in a negotiation. The persuasion side is covered in Technical Debt in Business Language.

5-4. Cautions when measuring

  • Watch the trend, not the absolute value. Comparisons against other teams or repositories almost always reach a wrong conclusion.
  • Never use the metrics for individual evaluation. The moment you do, the records distort.
  • Fill interest estimates with observations only. One unfounded number contaminates the credibility of the whole list.
  • If the payback period exceeds the team's planning horizon, that item is not one to repay now.

6. Building and maintaining the debt list

6-1. Without a list, debt becomes an emotional argument

The purpose of the list is not record-keeping but comparison. Only when items can be compared does prioritisation become arithmetic rather than argument.

# Example — the minimum fields of one debt item
id: DEBT-114
title: Dual writes remain on the orders currency column
type: data # code | design | test | dependency | data | ops | knowledge
location: services/order/**
quadrant: deliberate-prudent # Fowler quadrant
symptom: every order change must edit both columns; rollback risks inconsistency
interest: +4h per change, 6 changes/month → 24h/month # observed
principal: 10-15 person-days # estimate, as a range
payoff: run the contract phase (drop the old column after reads have moved)
risk_if_unpaid: schema changes become irreversible, rollback impossible
external_clock: none # if any, put the date here
owner: order-team
review_by: 2026-11-01 # re-evaluation date

6-2. Maintenance rules

  • Cap the number of items. A 200-item list is a warehouse, not a list. Past the cap, close the lowest-interest items.
  • Make the re-evaluation date a field. If you cannot re-observe the interest on that date, close the item.
  • Items with no observation stay candidates. "Feels bad" and "24 hours a month" must not sit in the same list.
  • Keep the history of closed items. Why you decided not to repay is the starting point of the next discussion.

6-3. Code comments are not a list

TODO and FIXME in code have the advantage of being discoverable, but they carry no priority, no owner and no expiry. The practical compromise is to connect the two: leave only the list item's identifier in the comment, and keep the interest and the plan in the list. Then you can navigate from code to item, and when the item closes the comment goes with it.


7. Ways to put repayment into the roadmap

[Four ways in — assumptions and failure modes]

1. Fixed percentage   allocate N% of every sprint's capacity to debt
   Assumes: debt is spread across many areas
   Fails when: the percentage is read as "spare time" and becomes zero

2. Dedicated period   one or two weeks per quarter on debt only
   Assumes: debt is concentrated in one area and comes in large lumps
   Fails when: normal speed resumes afterwards and the root cause is untouched

3. Bundled with features  repay only the debt on the path a feature crosses
   Assumes: debt and roadmap areas overlap
   Fails when: areas no feature crosses keep their debt forever

4. Gating            make repaying a specific debt a precondition for new work
   Assumes: that debt genuinely blocks the next task
   Fails when: overused — every feature becomes a hostage and trust erodes

7-1. This is contested

Whether dedicated debt sprints or a fixed capacity percentage works better is unresolved. The dedicated-period camp argues that large structural changes are impossible in fragmented time. The fixed-percentage camp argues that a dedicated period turns debt into a special event, teaching everyone that it is optional the rest of the time.

Three axes: whether debt is concentrated or spread, how rigid the organisation's planning cycle is, and whether the team is trusted to allocate its own time. In practice a mix shows up often: a fixed percentage for spread-out debt, a dedicated period for a big concentrated lump. That combination recommendation is this article's, not the sources'.

7-2. Repayment must also come in deployable pieces

Repayment usually takes the shape of refactoring, so the rules in The Complete Guide to Refactoring apply directly. Stay deployable no matter when you stop, split interface changes into expand-migrate-contract, and cut large repayments into reversible pieces. A three-week debt-repayment branch creates new debt instead of clearing old debt.

If repayment is needed at system scale, The Complete Guide to the Strangler Fig Pattern covers that structure, and the investment decision itself is in The Economics of Refactoring.


8. Deciding not to repay is also a decision

8-1. When you do not have to repay

  • Items with no observed interest: if there were zero changes in that area over six months, the interest is zero.
  • Code about to be retired: with a confirmed retirement date, repaying is waste. Check that "soon" has not been repeating for two years.
  • Payback period longer than the planning horizon: if payback takes three years and the service has a one-year lifespan, not repaying is correct.
  • Items where repayment risk exceeds the interest: a large refactoring in an area with no safety net belongs here.

8-2. Ignoring and accepting are different

Ignoring leaves no record. Accepting means it is written down who decided, when, on what grounds, and until when not to repay. Do not delete accepted items from the list; change their state. When the re-evaluation date arrives, observe the interest again, and if it grew, reopen.

8-3. What cannot be accepted

Items with an external clock are not eligible for acceptance. An unsupported runtime, an authentication method prohibited by a specification, or an already-published vulnerability is not something our judgement can defer. The spec items in section 3-1 belong here. Making this distinction a field on the list shortens the discussion.


9. Mechanisms that stop it accumulating again

9-1. Put the contract phase in the definition of done

The most common path by which debt re-accumulates is doing expand and migrate but never contract. Widen an interface in parallel, never remove the old path, and interest starts accruing from that moment. Adding "a removal ticket exists and has an expiry date" to the definition of done is the cheapest available defence.

9-2. Use review as the baseline

Google's code review standard says that reviewers should favour approving a change once it definitely improves the overall code health of the system being worked on, even if it is not perfect. Adopt that criterion verbatim and review becomes the gate against debt inflow. The same document recommends prefixing non-mandatory polish with "Nit: " and fixes one business day as the maximum time to respond to a review request.

Review speed matters for a separate reason. The same source notes that slow reviews discourage code cleanups, refactorings, and further improvements to existing changes. In an organisation with slow reviews, debt repayment structurally does not happen.

9-3. What to lock down with automation

- Lint exception lists: fail the build when the count grows. Only shrinking is allowed
- Deprecation warnings: commit in advance to a date when warnings become errors
- Dependency updates: turn on automated PRs and show the backlog count on a dashboard
- Flag expiry: fail CI on feature flags past their expiry date
- Coverage floor: gate on the coverage of changed files, not the global number
- Manual deploy steps: track the number of manual steps in the runbook as a metric

9-4. Blocking it up front with design docs

The Design Docs at Google article defines a design doc as an informal document written before coding that captures the high level implementation strategy and key design decisions with emphasis on the trade-offs. It also says to skip the doc when the solution is obvious and has no meaningful trade-offs; a document that has become an implementation manual without trade-off analysis is a sign you should just start coding.

That criterion transfers straight to debt prevention. Require a document only for hard-to-reverse decisions and the place where you spend documentation effort coincides with the place where the most expensive debt would otherwise appear.


Quiz: Check your understanding

Quiz 1: A team picked "the 10 most complex files" and built a repayment plan around them. What is missing?

Answer: Change frequency. A complex file nobody touches earns zero interest.

Explanation: Debt priority is set by interest, not principal. Interest is roughly "extra time this debt adds to one change" times "number of changes in that area". Pull the last 12 months of change counts from version control history and multiply by complexity to get a hotspot score, and the genuinely expensive places appear. Exclude generated files and mass formatting commits first.

Quiz 2: A debt item reads only "code is messy, three weeks to fix". What do you ask for?

Answer: Observed interest, the risk of not repaying, and a re-evaluation date.

Explanation: "Three weeks" is the principal, and principal alone cannot be compared with other items. The symptom has to be written observably — "+4 hours per change, 6 changes a month" yields a comparable 24 hours a month. Add the risk of leaving it unpaid and a re-evaluation date and the item has earned its place on the list.

Quiz 3: A service retries with no backoff and no budget, and has had no incidents in six months. How do you write its interest?

Answer: As an expected value: probability of occurrence times cost per incident. State explicitly that day-to-day interest is zero.

Explanation: Operational debt shows no cost on ordinary days and bills everything during an incident, so writing only observed time costs keeps it losing on priority. The SRE Book recommends always scheduling retries with randomised exponential backoff and offers a retry budget such as 60 per minute in a process. It also notes that three layers retrying four times each turn one user action into 64 attempts — that amplification factor is usable as the basis of the interest calculation.

Quiz 4: Leadership says "we need speed this quarter, so let's defer debt repayment". What can you answer with?

Answer: DORA's finding that speed and stability are not trade-offs and that for most teams the metrics are correlated.

Explanation: DORA states that over long periods the real trade-off is between better software faster and worse software slower. That is a general result, though, so pair it with your own team's change lead time and change fail rate trends to be persuasive. Deferring a specific item can be a legitimate decision — record it as accepted and set a re-evaluation date.

Quiz 5: A service runs on an unsupported runtime. Can that item be marked "accepted"?

Answer: No. Items with an external clock are not something our judgement can defer.

Explanation: Items whose repayment date we choose and items whose deadline is set from outside are different in kind. Unsupported runtimes, authentication methods prohibited by a specification, and already-published vulnerabilities are the latter. For example RFC 9700, the OAuth 2.0 security best current practice, states that the resource owner password credentials grant must not be used. Keeping an external-clock field with a date on the list stops such items mixing in with ordinary ones.


Wrapping up

The hardest part of technical debt is not fixing it. It is finding where it lives, attaching comparable numbers to it, and keeping those numbers current. With that work done, repayment decisions are mostly easy; without it, no persuasion technique lasts long.

Compressed into one line: filter out what is not debt, find what changes often and is hard to change, write the interest from observations, compare through a list, cut repayment into deployable pieces, and record what you decided not to repay along with the grounds and the re-evaluation date. Then put the contract phase into the definition of done so it does not pile up again.


References

  • Technical Debt Quadrant — Martin Fowler — cited for the deliberate/inadvertent and reckless/prudent axes, the character of the four quadrants, the observation that it can take a year of programming before you understand the best design approach, and the point that the metaphor holds across all four. Checked 2026-08-15.
  • DORA metrics: the four keys — DORA — cited for the definitions of change lead time, change fail rate, failed deployment recovery time and rework rate, and for the conclusion that speed and stability are not trade-offs. Checked 2026-08-15.
  • Addressing Cascading Failures — Google SRE Book — cited for the definition of cascading failure, the randomised exponential backoff recommendation, the 60-retries-per-minute budget example, and the three-layers-times-four-retries calculation. Checked 2026-08-15.
  • Design Docs at Google — Industrial Empathy — cited for the definition of a design doc and its trade-off emphasis, the advice to skip it when the solution is obvious, and the observation that docs get out of sync with reality. Checked 2026-08-15.
  • Code Review Developer Guide — Google — cited for approving a change that definitely improves overall code health even when imperfect, and the "Nit: " convention; and the review speed document for slow reviews discouraging cleanups and refactorings and the one-business-day standard. Checked 2026-08-15.
  • RFC 9700 — OAuth 2.0 Security Best Current Practice — cited for the prohibition on the resource owner password credentials grant and the recommendation on the implicit grant. Checked 2026-08-15.
  • OWASP Session Management Cheat Sheet — cited for the recommendation not to keep authentication tokens and session identifiers in browser storage, and the reason. Checked 2026-08-15.
  • The debt test in section 2, the type table in section 3, the hotspot calculation and trap list in section 4, the interest formula in section 5, the item template and maintenance rules in section 6, the four roadmap approaches in section 7, the acceptance criteria in section 8 and the automation list in section 9 are procedures assembled in this article rather than taken from the sources above.

Further reading

The Complete Guide series