---
title: 'Technical Debt Management'
source: 'https://academia.sh/en/courses/architectural-documentation/technical-debt-management'
course: 'Architectural Decisions and Documentation'
language: en
updated: '2026-08-23T07:01:04+00:00'
license: 'CC BY-SA 4.0'
---

# Technical Debt Management

Separating and measuring deliberate from inadvertent debt: debt items are split into two classes, debt interest is modeled as the difference in work the same change causes in an indebted module versus a debt-free one, yearly interest per item and the rate of extra work per module are computed, the delay before inadvertent debt is noticed is measured as a function of a repeat threshold, and the interest paid unnoticed across that delay is counted.

A risk register tracks something not yet realized: an event with a likelihood that has not
happened yet. Next to it stands a second record, and that one tracks something already realized.
When a decision is made, sometimes an incomplete solution is chosen deliberately; the gap stays in
the system and produces extra work with every change. The name for this gap is **technical debt**.

How technical debt gets defined as a metric was addressed in the Quality Metrics lesson; it is not
repeated here. This lesson has two questions. First: once debt items are split into two classes —
taken knowingly and taken without realizing it — does that split produce a measurable difference.
Second: what is **debt interest**, and how is it counted.

## Two Classes and Interest

**Deliberate debt** is a gap chosen knowingly at decision time: to ship faster, or because the
right solution is not yet known. It can be recorded, because the person who chose it knows they
chose it. **Inadvertent debt** is not chosen; it is understood only later that an assumption was
wrong, or that a copy landed in two places. It cannot be recorded, because no one is aware of it
while the record is being written.

The way to measure interest reduces to a single question: how many more minutes does the same
change take in the indebted module than in a debt-free one. This does not say the debt is "bad" —
it says how bad. In the model below, five change types, their base durations, and their yearly
counts are the model's inputs (**DR14**). Every debt item carries which change types it affects and
how many extra minutes it adds for that type. The scenario is fictional.

```js
// debt/interest.mjs — debt items are split into two classes, interest is measured as extra work per change
// Change types, base durations, and yearly counts are the model's input (DR14); the scenario is fictional.
export const CHANGE_TYPES = [
  { type: "rule-change", base: 120, yearly: 12 },
  { type: "new-field", base: 90, yearly: 8 },
  { type: "new-screen", base: 240, yearly: 6 },
  { type: "bug-fix", base: 60, yearly: 30 },
  { type: "performance-tuning", base: 150, yearly: 4 },
];

// extraWork: how many extra minutes the same change takes in the indebted module vs. the debt-free module
export const ITEMS = [
  { name: "rule-copy", class: "deliberate", module: "loan", extraWork: 35,
    affects: ["rule-change", "bug-fix"] },
  { name: "manual-field-mapping", class: "deliberate", module: "catalog-mapping", extraWork: 45,
    affects: ["new-field", "bug-fix"] },
  { name: "temp-membership-table", class: "deliberate", module: "membership", extraWork: 20,
    affects: ["new-field", "new-screen"] },
  { name: "fixed-report-query", class: "deliberate", module: "reporting", extraWork: 30,
    affects: ["new-screen"] },
  { name: "untested-penalty-branch", class: "inadvertent", module: "loan", extraWork: 40,
    affects: ["rule-change", "bug-fix", "performance-tuning"] },
  { name: "date-format-in-two-places", class: "inadvertent", module: "catalog-mapping", extraWork: 25,
    affects: ["new-field", "bug-fix"] },
  { name: "hidden-branch-assumption", class: "inadvertent", module: "membership", extraWork: 55,
    affects: ["new-screen", "bug-fix"] },
  { name: "duplicated-authorization-check", class: "inadvertent", module: "reporting", extraWork: 35,
    affects: ["new-screen", "new-field", "bug-fix"] },
  { name: "manual-cache-cleanup", class: "inadvertent", module: "loan", extraWork: 15,
    affects: ["performance-tuning", "bug-fix"] },
];

export const MODULES = [...new Set(ITEMS.map((k) => k.module))];
export const yearly = (type) => CHANGE_TYPES.find((d) => d.type === type).yearly;
export const itemInterest = (k) => k.extraWork * k.affects.reduce((t, type) => t + yearly(type), 0);
export const baseWork = CHANGE_TYPES.reduce((t, d) => t + d.base * d.yearly, 0);  // per module

if (import.meta.url.endsWith(process.argv[1].split("/").pop())) {
  const deliberate = ITEMS.filter((k) => k.class === "deliberate");
  console.log(`${ITEMS.length} debt items: ${deliberate.length} deliberate (recorded), ` +
    `${ITEMS.length - deliberate.length} inadvertent (unrecorded)`);
  console.log(`${CHANGE_TYPES.reduce((t, d) => t + d.yearly, 0)} changes a year per module, ` +
    `${baseWork} min in a debt-free module\n`);

  console.log("yearly interest per item (extra work per change x yearly change count):");
  for (const k of [...ITEMS].sort((a, b) => itemInterest(b) - itemInterest(a)))
    console.log(`  ${k.name.padEnd(31)} ${k.class.padEnd(11)} ${k.module.padEnd(15)} ` +
      `${String(k.extraWork).padStart(2)} min x ${String(k.affects.reduce((t, type) => t + yearly(type), 0)).padStart(2)} ` +
      `changes = ${String(itemInterest(k)).padStart(4)} min/yr`);

  console.log("\ntotal interest per class:");
  for (const s of ["deliberate", "inadvertent"]) {
    const g = ITEMS.filter((k) => k.class === s);
    const f = g.reduce((t, k) => t + itemInterest(k), 0);
    console.log(`  ${s.padEnd(11)} ${g.length} items, ${f} min/yr, per item ${(f / g.length).toFixed(0)} min`);
  }

  console.log("\nyearly work per module (base + interest):");
  for (const m of MODULES) {
    const f = ITEMS.filter((k) => k.module === m).reduce((t, k) => t + itemInterest(k), 0);
    console.log(`  ${m.padEnd(15)} base ${baseWork.toFixed(0)} + interest ${String(f).padStart(4)} = ` +
      `${(baseWork + f).toFixed(0)} min  (${(100 * f / baseWork).toFixed(1)}% extra)`);
  }
}
```

```
9 debt items: 4 deliberate (recorded), 5 inadvertent (unrecorded)
60 changes a year per module, 6000 min in a debt-free module

yearly interest per item (extra work per change x yearly change count):
  hidden-branch-assumption        inadvertent membership      55 min x 36 changes = 1980 min/yr
  untested-penalty-branch         inadvertent loan            40 min x 46 changes = 1840 min/yr
  manual-field-mapping            deliberate  catalog-mapping 45 min x 38 changes = 1710 min/yr
  duplicated-authorization-check  inadvertent reporting       35 min x 44 changes = 1540 min/yr
  rule-copy                       deliberate  loan            35 min x 42 changes = 1470 min/yr
  date-format-in-two-places       inadvertent catalog-mapping 25 min x 38 changes =  950 min/yr
  manual-cache-cleanup            inadvertent loan            15 min x 34 changes =  510 min/yr
  temp-membership-table           deliberate  membership      20 min x 14 changes =  280 min/yr
  fixed-report-query              deliberate  reporting       30 min x  6 changes =  180 min/yr

total interest per class:
  deliberate  4 items, 3640 min/yr, per item 910 min
  inadvertent 5 items, 6820 min/yr, per item 1364 min

yearly work per module (base + interest):
  loan            base 6000 + interest 3820 = 9820 min  (63.7% extra)
  catalog-mapping base 6000 + interest 2660 = 8660 min  (44.3% extra)
  membership      base 6000 + interest 2260 = 8260 min  (37.7% extra)
  reporting       base 6000 + interest 1720 = 7720 min  (28.7% extra)
```

The item table's first piece of information is in the ranking: the highest-interest item is not
the one that adds the most work per change. temp-membership-table adds 20 minutes per change and
costs 280 minutes a year; manual-cache-cleanup adds 15 minutes and costs 510. The difference is in
how many changes the item touches. A debt item's weight is measured not by how ugly it is, but by
how often it gets set off.

The class table gives the second piece of information. Five inadvertent items cost 6820 minutes a
year, four deliberate items 3640; 1364 minutes per item against 910. Inadvertent debt is more
expensive per item. The reason is structural, not moral: deliberate debt is taken as part of a
decision, and its scope is thought through when it is taken, while inadvertent debt grows without
anyone watching. The measured difference is the numeric counterpart of these two states not being
the same thing.

The module table compares interest against base work. In the loan module, the same yearly set of
changes takes 9820 minutes, against 6000 in a debt-free module — 63.7 percent extra work. In the
reporting module, the same ratio is 28.7 percent. It cannot be said the two modules do different
work — exactly the same set of changes was run. The entire gap between them is debt.

## The Delay Before Noticing

Deliberate debt has zero delay before entering the record: it is known the moment it is chosen.
Inadvertent debt is noticed at some point, and its interest keeps being paid until then. Noticing
does not happen through a single painful change — it happens through the same extra work
repeating: someone sees a pattern once they get stuck in the same place on two different tasks. In
the model, an item is noticed once it has produced the same extra work a threshold number of times
(**DR15**). This means the delay is tied not to the item's size, but to how often the changes it
touches occur.

```js
// debt/delay.mjs — the delay before inadvertent debt is noticed, and the interest paid unnoticed until then
import { ITEMS, yearly, itemInterest } from "./interest.mjs";

// An item is noticed once the same extra work has shown up THRESHOLD times; changes are spread
// evenly across the year. Both are the model's input (DR15).
const THRESHOLD = [8, 24, 48];
const MONTHS = 12;
const affected = (k) => k.affects.reduce((t, type) => t + yearly(type), 0);
const delay = (k, threshold) => (MONTHS * threshold) / affected(k);
const inadvertent = ITEMS.filter((k) => k.class === "inadvertent");

console.log(`${inadvertent.length} inadvertent items; deliberate debt has zero delay because it is written into the record`);
console.log("threshold  average delay  unnoticed at month 12  interest paid unnoticed  share of yearly interest");
const yearlyInterest = inadvertent.reduce((t, k) => t + itemInterest(k), 0);
for (const threshold of THRESHOLD) {
  const delays = inadvertent.map((k) => delay(k, threshold));
  const paid = inadvertent.reduce((t, k) => t + threshold * k.extraWork, 0);
  console.log(`  ${String(threshold).padStart(2)}  ${(delays.reduce((a, b) => a + b, 0) / delays.length).toFixed(1).padStart(13)} mo ` +
    `${String(delays.filter((x) => x > MONTHS).length).padStart(20)}  ${String(paid).padStart(21)} min ` +
    `${((100 * paid / yearlyInterest).toFixed(0) + "%").padStart(15)}`);
}

console.log(`\nper item (threshold 24; yearly inadvertent interest ${yearlyInterest} min):`);
for (const k of [...inadvertent].sort((a, b) => delay(a, 24) - delay(b, 24)))
  console.log(`  ${k.name.padEnd(31)} ${String(affected(k)).padStart(2)} changes/yr, ` +
    `${delay(k, 24).toFixed(1).padStart(4)} mo until noticed, ${24 * k.extraWork} min paid until then`);

// Question set: 9 items x 4 questions; based on which items are in the record by month 12
const QUESTIONS = ["B1 was this gap chosen deliberately", "B2 when did it enter",
  "B3 what was the payback condition", "B4 how much extra work does it cause per change"];
console.log(`\nquestion set at month 12 (${ITEMS.length} items x ${QUESTIONS.length} questions = ` +
  `${ITEMS.length * QUESTIONS.length} answers):`);
// A deliberate item answers all four questions; a noticed inadvertent item answers B1 and B4,
// B2 and B3 stay missing; a never-noticed item answers B1 wrong, the rest is missing.
const DELIBERATE_COUNT = ITEMS.length - inadvertent.length;
for (const threshold of THRESHOLD) {
  const found = inadvertent.filter((k) => delay(k, threshold) <= MONTHS).length;
  const outside = inadvertent.length - found;
  console.log(`  threshold ${String(threshold).padStart(2)}: in record ${DELIBERATE_COUNT + found}/${ITEMS.length} items -> ` +
    `correct ${4 * DELIBERATE_COUNT + 2 * found}, missing ${2 * found + 3 * outside}, wrong ${outside}`);
}
```

```
5 inadvertent items; deliberate debt has zero delay because it is written into the record
threshold  average delay  unnoticed at month 12  interest paid unnoticed  share of yearly interest
   8            2.5 mo                    0                   1360 min             20%
  24            7.4 mo                    0                   4080 min             60%
  48           14.7 mo                    5                   8160 min            120%

per item (threshold 24; yearly inadvertent interest 6820 min):
  untested-penalty-branch         46 changes/yr,  6.3 mo until noticed, 960 min paid until then
  duplicated-authorization-check  44 changes/yr,  6.5 mo until noticed, 840 min paid until then
  date-format-in-two-places       38 changes/yr,  7.6 mo until noticed, 600 min paid until then
  hidden-branch-assumption        36 changes/yr,  8.0 mo until noticed, 1320 min paid until then
  manual-cache-cleanup            34 changes/yr,  8.5 mo until noticed, 360 min paid until then

question set at month 12 (9 items x 4 questions = 36 answers):
  threshold  8: in record 9/9 items -> correct 26, missing 10, wrong 0
  threshold 24: in record 9/9 items -> correct 26, missing 10, wrong 0
  threshold 48: in record 4/9 items -> correct 16, missing 15, wrong 5
```

The threshold table gives the cost of the delay. Debt noticed after eight repeats enters the
record after an average of 2.5 months and extracts 1360 minutes of payment until then; at
twenty-four repeats, 7.4 months and 4080 minutes. A pattern requiring forty-eight repeats is not
noticed at all within a year, and the interest paid rises to 120 percent of the yearly inadvertent
interest — meaning no one knows what is slowing things down for more than a year. The difference
across these three rows comes down to one thing: how long it takes for the extra work to be seen
as a pattern.

The item table shows the outcome of that link, and it runs against intuition.
hidden-branch-assumption is the highest-interest item — 1980 minutes a year — but it is one of the
latest to be noticed, at 8.0 months. untested-penalty-branch carries less interest and is noticed
at 6.3 months. The reason is the model's rule: noticing depends not on how much each occurrence
hurts, but on how many times it repeats. A debt that lands a few heavy hits stays invisible longer
than one that lands frequent light ones. If the noticing rule were tied to size, the order would
reverse; the measured result depends on the rule itself, and the result cannot be interpreted
without the rule being written down.

The question set table comes out the same at two thresholds and splits at the third. A noticed
inadvertent debt item answers two of its four questions: it can be said that it was not chosen
deliberately, and the extra work it causes per change can be measured. When it entered and what
its payback condition was stay unanswered, because that information was never written when the
item entered and cannot be produced afterward. This is the one real advantage deliberate debt has
over inadvertent debt: it pays the same interest, but it answers all four questions.

## Summary

- A debt item's yearly interest is the product of the work it adds per change and the number of
  changes it touches; the highest-interest item is not the one adding the most work per change.
- Five inadvertent items cost 6820 minutes a year, four deliberate items 3640; 1364 minutes per
  item against 910, meaning inadvertent debt is more expensive per item.
- The same yearly set of changes takes 9820 minutes in the loan module against 6000 in a debt-free
  module — 63.7 percent extra work; in the reporting module the same ratio is 28.7 percent.
- The delay before inadvertent debt is noticed depends on the repeat threshold: 2.5 months and
  1360 minutes at eight repeats, 7.4 months and 4080 minutes at twenty-four, and at forty-eight it
  is not noticed at all within a year, with the interest paid rising to 120 percent of the yearly
  inadvertent interest.
- The order of noticing is not the same as the order of interest; the highest-interest item is
  among the latest noticed, at 8.0 months, because in this model noticing is tied to repetition,
  not to size.

## Next Step

Everything written across this topic gathered around a single decision. The decision record holds
the decision's rationale, the proposal process holds how the decision was reached, the trade-off
analysis holds what was traded for what, the risk register holds what might not hold, the debt
record holds what was deliberately left incomplete. Five record formats, and a measured question
set for each.

All five share one limit: each is the record of a single decision. Someone new to the library
network's system, reading every one of these records, learns the rationale behind forty decisions
but does not learn what the system looks like. Which parts exist, which depends on which, where
data flows from and to, which part sits on which machine — none of these questions is answered in
the record of any single decision, because none of them belongs to a single decision. Stacking the
records on top of one another gives no picture either; the sum of forty records is not a map of
the system. The next topic takes on that gap: it measures what a description showing the system as
a whole is, how many distinct views it needs, and which question each view answers.
