Skip to content

Tech debt at scale: hotspots and a debt portfolio

Debt interest gathers where complex code changes often: rank files by commits times complexity, fund the top, and judge it by that area's delivery.

Predict first

Before you read: predict

ExamHall's tech lead wants next quarter's debt work to go into rewriting exports/legacy-pdf.ts, the longest file in the monorepo. Before the planning meeting the team runs a hotspot analysis over the last six months: code-complexity 4.4.4, with lines of code as its complexity measure. Today is 15 April 2026.

file                      lines of code   commits since 15 Oct   of them fix: commits   people committing
grading/rules.ts                  1,900                     41                      6                   5
proctor/flags.ts                     60                     88                     10                   9
i18n/messages.ts                    900                     22                      1                  14
candidate/autosave.ts               420                     35                     30                   4
exports/legacy-pdf.ts             2,400                      0                      0                   0

Which file does the analysis put first?

Pick one

The 30-second version

Interest is paid on changes, and version control records where changes go. Count each file's commits over recent months, multiply by a complexity measure such as lines of code, and the top of that list is where debt costs most. Pay it from a standing share of capacity or an outcome-framed project, and watch how changes there move afterwards.

Debt interest gathers where complex code changes often: rank files by commits times complexity, fund the top, and judge it by that area's delivery.

How it works

Step 1 of 14

Interest follows change. Debt costs only when code is changed (se-technical-debt step 2); version control records every change.
In the kitchen
  • the till's record of every hire → version control's record of every change (step 1)

Analogy: A tool-hire depot's workshop

At the depot In the codebase
the till's record of every hire version control's record of every change (step 1)
hires per tool this season commits per file in the window (steps 2, 6)
the workshop's wear grade on each tool lines of code as the complexity measure (step 3)
hires times wear, worked down from the top the hotspot list (step 4)
one workshop day a week, every week a standing share of capacity (step 9)
hires cut short by a breakdown, before and after the service the area's changes, before and after (steps 11, 13)

In real terms: count where changes go, weight each file by how hard its code is, and spend a steady share of time at the top of that list, judged by how changes there go afterwards.

Breaks down when: a worn tool fails outright, while tangled code keeps working and only makes each change slower and riskier.

Contrasting case: a display of antique tools that are never hired out: wear with no hires costs the depot nothing.

Examples, run for real

4.1 ExamHall EXH-4370: the rewrite proposal, checked against six months of history

EXH-4370: ExamHall's tech lead proposed spending next quarter rewriting exports/legacy-pdf.ts, the longest file in the monorepo. Before deciding, the team ran code-complexity 4.4.4 over the last six months of the fixture monorepo. MR !4670 records the list and funds work on its top file instead.

4.1 EXH-4370 code-complexity 4.4.4, lines of code, 2025-10-15 to 2026-04-15: 7 of 8 files listed
4.1 #1 grading/rules.ts         41 commits × 1,900 lines = 77,900
4.1 #2 i18n/messages.ts         22 commits ×   900 lines = 19,800
4.1 #3 candidate/autosave.ts    35 commits ×   420 lines = 14,700
4.1 #4 proctor/flags.ts         88 commits ×    60 lines = 5,280
4.1 #5 reports/term-summary.ts   6 commits ×   700 lines = 4,200
4.1 #6 grading/export-csv.ts     9 commits ×   300 lines = 2,700
4.1 #7 candidate/timer.ts       12 commits ×   150 lines = 1,800
4.1 exports/legacy-pdf.ts: 2,400 lines, last commit 2022-06-11, not listed
4.1 MR !4670 funds grading/rules.ts, the top entry

The exporter is 2,400 lines, but nobody committed to it in six months, so the tool does not list it (steps 2, 5). grading/rules.ts is shorter and changed in 41 commits, so it tops the list by a wide margin (step 4). proctor/flags.ts has the most commits, but at 60 lines each change is cheap.

4.2 MR !4670: the grading area, before and after

MR !4670 replaced the if-chains in grading/rules.ts with one rule table per question type, in the same file, paid for from a standing share of the grading team's sprints. It landed in May 2026. The team compared the changes to grading/rules.ts in the first quarter of 2026 with those in the third. The records are synthetic.

4.2 changes to grading/rules.ts, Q1 2026, before MR !4670: 18 · median lead time 6 days · needed a rollback or hotfix: 5 of 18 (28%)
4.2 changes to grading/rules.ts, Q3 2026, after it: 21 · median lead time 2 days · needed a rollback or hotfix: 2 of 21 (10%)
4.2 MR !4670's own commits: 14, 2,300 lines added + deleted

The refactor's own size says the work was done; the area's changes before and after say what it bought (steps 11–13).

Try it

Answer first, then check. Nothing is saved.

Question 1 of 8

A ticketing platform's team will fund one refactor next quarter and builds a hotspot list from its repository's history to choose it, with lines of code as the complexity measure. Today is 15 October 2026. src/generated/ticketing-api.ts is rewritten by openapi-generator from the API spec whenever the spec changes; nobody edits it by hand.

file                             lines of code   commits, last 12 months   commits before that
src/generated/ticketing-api.ts           3,000                        48                    10
pricing/legacy-fees.ts                   2,200                         2                    95
venue/seat-map.ts                        1,400                        18                     6
reports/sales-export.ts                    900                        10                    12
seat-hold/holds.ts                         800                        40                    20
checkout/cart.ts                           300                        64                     8

Which file should top the team's hotspot list?

Pick one

Question 2 of 8

In June the same team split seat-hold/holds.ts, the top of that list, into a slimmer seat-hold/holds.ts plus seat-hold/expiry.ts and seat-hold/release-rules.ts. These are its changes to seat-hold/ in the quarter before the split and the quarter after. Each change is one commit, and its lead time runs from that commit to production:

change   quarter   files touched                                  lead time (days)
SH-101   Q2        seat-hold/holds.ts                                            9
SH-102   Q2        seat-hold/holds.ts                                           12
SH-103   Q2        seat-hold/holds.ts                                            7
SH-104   Q2        seat-hold/holds.ts                                           10
SH-105   Q2        seat-hold/holds.ts                                            8
SH-131   Q3        seat-hold/expiry.ts                                           2
SH-132   Q3        seat-hold/holds.ts, seat-hold/expiry.ts                       8
SH-133   Q3        seat-hold/release-rules.ts                                    3
SH-134   Q3        seat-hold/expiry.ts                                           3
SH-135   Q3        seat-hold/holds.ts                                            6
SH-136   Q3        seat-hold/release-rules.ts                                    2
SH-137   Q3        seat-hold/holds.ts, seat-hold/release-rules.ts                9
SH-138   Q3        seat-hold/expiry.ts                                           4
SH-139   Q3        seat-hold/holds.ts, seat-hold/expiry.ts                       8

What happened to the median change lead time of the team's seat-hold changes?

Pick one

Question 3 of 8

A second ticketing team keeps a standing 25% share of its capacity for its top hotspot, venue/seat-map.ts, from sprint 1; nothing else draws on the share. The refactor is estimated at 32 engineer-days. Sprints are two weeks of 10 working days. The team has 4 engineers in sprints 1 and 2, and 8 from sprint 3 on.

In which sprint does the refactor finish?

Pick one

Question 4 of 8

In a mock interview you proposed paying down a ticketing platform's top hotspot, seat-hold/holds.ts, with a refactor estimated at 36 engineer-days. The interviewer follows up: "The team is 6 engineers in two-week sprints of 10 working days. The product lead will not stop feature work in any sprint, and the season opens after sprint 4: by then the refactor must be finished and features must have had at least 180 engineer-days. What do you propose? Three candidates answered:

Whose answer survives the follow-up?

Pick one

Question 5 of 8

A mock interview turns to debt work you have seen funded.

How do you answer, in at most 150 words and with one project you led or saw (a ticketing example will do), the interviewer's question "How did you get leadership to fund a debt project, and how did you show it worked"?

Question 6 of 8

A hiring manager wants an answer you could say aloud.

What do you write, in at most 120 words, to their question "In a large codebase, how do you decide which tech debt to pay first"?

Question 7 of 8

A payments-ledger team builds a hotspot list over the last six months of its repository, with lines of code as the complexity measure. Today is 15 October 2026.

file                    lines of code   commits, last 6 months   of them since 15 Sep
posting/rules.ts                1,500                       30                      2
settlement/batch.ts             2,600                       12                      6
payouts/schedule.ts               500                       45                     20
fx/rates.ts                       300                       70                     12
fx/round.ts                        40                       16                      3

Which file tops the list?

Pick one

Question 8 of 8

A chat app's team builds a hotspot list over the last six months of its repository, with lines of code as the complexity measure. Today is 15 October 2026. On 2 July one commit, style: apply prettier 3, reformatted every file in the repository; both commit columns below include it.

file                    lines of code   commits, last 6 months   of them since 2 Jul
presence/tracker.ts             1,100                        7                      2
chat/send.ts                      250                       30                     10
media/thumbs.ts                   500                       12                      9
rooms/members.ts                  200                       20                      8

Which file should top the list?

Pick one

Go deeper

Pro tip
  1. Count commits per file with plain git before reaching for a tool.
    git log --since=2025-10-15 --format= --name-only | sort | uniq -c | sort -rn | head -3
    When: in the first minutes of a debt discussion, to see where change goes; it gives one axis of the matrix, not the list (step 2).
tip 1 git log --since=2025-10-15 --format= --name-only, counted: 88 proctor/flags.ts · 41 grading/rules.ts · 35 candidate/autosave.ts · same counts as the tool's churn column
Source
  • Adam Tornhill, Your Code as a Crime Scene, 2nd ed. (Pragmatic Bookshelf, 2024) (the row's first source): a book, not fetched; it keys nothing here. The hotspot idea (change frequency from version control times a complexity measure, with lines of code as a usable first measure) is taught from it in steps 2–5 and executed with the tool below.
  • Martin Fowler, "Technical Debt Quadrant" (the row's second source): https://martinfowler.com/bliki/TechnicalDebtQuadrant.html · fetched 2026-10-15 with curl. The quadrant and the interest metaphor belong to the prerequisite; this lesson quotes nothing from it. Valid as of 2026-10-15.
  • code-complexity 4.4.4 (the tool every hotspot item runs): README at the v4.4.4 tag, https://github.com/simonrenoult/code-complexity/blob/v4.4.4/README.md · fetched 2026-10-15: "Measure the churn/complexity score. Higher scores reveal hotspots where refactorings should happen." (its opening blockquote) and "lines of code count (all languages)" (steps 3–4); its options --since, --filter and --sort are the ones the scripts pass. Its installed source (dist/src/lib/statistics/statistic.js) computes this.score = this.churn * this.complexity, and its churn adds one per file name in git log --name-only, so one per commit. Valid as of 2026-10-15.
  • Git, "git-log" documentation: https://git-scm.com/docs/git-log · fetched 2026-10-15: --since=<date> ("Show commits more recent than <date>."), --name-only ("Show only the name of each changed file in the post-image tree.") and --numstat ("shows number of added and deleted lines in decimal notation"), as the scripts and tip 1 use them. Valid as of 2026-10-15.

Checked against the tools and docs as of 2026-10-15.