Skip to content
Back to Maintenance & LTS

Debt Identification

Surface the shortcuts, workarounds, and decay in your codebase before they become emergencies.

Why

Technical debt is invisible until it isn't — a shortcut taken in sprint 3 becomes the 2-day debugging session in sprint 20. Identification makes the invisible visible: you catalog the TODOs, the duplicated logic, the untested paths, the framework version two majors behind, and suddenly you have a list you can manage instead of a vague dread. The goal isn't zero debt (that's impossible and undesirable — debt is a tool for speed) but known debt with a known cost. Teams that don't identify debt are always surprised by estimates that blow up, because the debt is the reason a "simple" feature takes a week, and nobody connected the two.

When

Continuously — tag debt in code as you create it (TODO(debt):), and run a quarterly inventory to surface what's accumulated.

How

1. Tag debt at creation: every shortcut gets a TODO(debt): <reason> comment with a ticket link
2. Run static analysis: eslint (complexity, duplication), knip (dead code), dependency-cruiser (architecture)
3. Search for debt markers: grep -rn "TODO(debt)|FIXME|HACK|XXX" src/
4. Catalog in a DEBT.md or issue tracker: each item has type, location, estimated cost, interest rate (how fast it compounds)
5. Classify: code debt (duplication, complexity) | design debt (coupling, missing abstraction) | test debt (untested paths) | dep debt (outdated)
6. Measure: track cyclomatic complexity, duplication %, test coverage, outdated-dep count as trend lines
7. Review the catalog quarterly; items that haven't been touched in 2 quarters are candidates for explicit acceptance

Example

# Tag debt at creation
// TODO(debt): duplicated validation logic — extract to shared schema
// Ticket: https://tracker/issues/DEBT-42
function validateOrder(o) { /* copied from validateUser */ }

# Quarterly inventory script
grep -rn "TODO(debt)|FIXME|HACK" src/ > debt-raw.txt
npx eslint --rule '{"complexity":["error",15],"no-dupe-keys":"error"}' src/
npx knip --include dependencies,files,exports
npx dependency-cruiser --validate .dependency-cruiser.js src/

# DEBT.md catalog
| ID  | Type   | Location        | Cost  | Interest | Age  |
|-----|--------|-----------------|-------|----------|------|
| D1  | code   | order.ts:dup    | 2h    | high     | 3mo  |
| D2  | test   | payment.ts:0cov | 4h    | critical | 6mo  |
| D3  | dep    | react@16        | 2d    | high     | 9mo  |

Common Problems

  • ⚠Untagged debt — a shortcut taken without a TODO means nobody knows it's a shortcut, so it's treated as the design and never paid.
  • ⚠Measuring only code debt (complexity) and ignoring test debt — untested payment code is higher-risk than a complex utility.
  • ⚠The debt catalog exists but is never reviewed — it grows to 500 items and becomes a guilt list, not an action list.

Best Practices

  • ✓Tag every shortcut with TODO(debt): + a ticket at creation time — debt created without a tag is debt you'll forget you owe.
  • ✓Track debt as trend lines (complexity, duplication %, outdated deps), not just a static list — a flat or falling line is the goal.
  • ✓Review the debt catalog quarterly and explicitly accept items older than 2 quarters — acceptance is a valid outcome, denial is not.