What Technical Debt Actually Is
A design or implementation choice that increases the cost of future change. Not ugliness, not old code, not a library you would not have picked — and the metaphor was originally about deliberate shortcuts.
The requirement, the obvious build, and why it breaks
Every lesson starts where the work starts: someone asked for something, and the first implementation that comes to mind survives until the requirement changes.
Half the backlog is labelled "tech debt". Which of it is actually debt, and how would we tell?
Engineering asks for a quarter to "pay down technical debt". The list has forty items, from "upgrade the test runner" to "the checkout module has no tests" to "we use Redux and I prefer Zustand".
Technical debt is the accumulated mess in the codebase. Keep a list of everything that is not how we would do it today, and spend some percentage of every sprint reducing the list.
The list is unbounded, because "not how we would do it today" grows every time the team learns something. It will never be finished, so it never gets funded, and the framing teaches the business that engineering wants budget for things it cannot justify.
- The list is unbounded, because "not how we would do it today" grows every time the team learns something. It will never be finished, so it never gets funded, and the framing teaches the business that engineering wants budget for things it cannot justify.
- It mixes items whose costs differ by three orders of magnitude. "No tests on checkout" and "inconsistent import ordering" appear as peers, so prioritisation collapses into advocacy.
- It makes the word useless in the conversation where it matters. When everything is debt, saying "this is debt" carries no information, and a genuine, expensive, bounded shortcut gets the same shrug as a style preference.
- It hides the actual decision. Most items are not debt at all — they are a bet nobody has priced, or a dependency someone dislikes, or code that is perfectly fine and simply unfamiliar (The Legacy Change Loop).
What limits the solution, and what must never stop being true
This domain leads with these two. A design that ignores its constraints is not a design, and an invariant nobody named is one nothing is protecting.
- The company will fund at most three weeks of work with no customer-visible output.
- Nobody can say what any item on the list costs the business, so the list is being prioritised by who argued hardest.
- Two items on the list conflict: one proposes extracting a module, another proposes deleting the feature that module serves.
- Any claim that something is debt must name a future change it makes more expensive. Without that, the word is being used to mean preference.
- Whatever is paid down, behaviour must not change; a debt-repayment project that ships behaviour changes is a rewrite with a friendly name (What Refactoring Actually Is).
Who owns what, and where the seams fall
Responsibilities decide boundaries; boundaries decide what an interface has to say.
- Whoever claims something is debt owns naming the change it makes expensive, and roughly how much (Interest: Why Debt Compounds).
- Whoever took the shortcut owns recording it at the time, because that is the only moment the reasoning is available (Deliberate Debt).
- The team owns the register and its size; a register nobody prunes is a wishlist (The Debt Register).
- The line is between a *choice with a change-cost consequence* and everything else: unfamiliarity, taste, obsolescence, and ordinary incompleteness.
- Debt is bounded by the area whose changes it makes expensive. Debt in a module nobody edits has no interest payments and belongs at the bottom of any list (When Design Does Not Pay).
- The boundary is not "old versus new". A ten-year-old module with tests and one owner is not debt; last month's untested integration is (What "Legacy" Actually Means).
Four quadrants, four different responses
The most useful thing anyone has added to the metaphor is the split into deliberate versus accidental and prudent versus reckless. It matters because the four cells need different responses, and lumping them together is why debt conversations go nowhere.
Note where the original metaphor sits: top-left. Cunningham was describing shipping against an imperfect understanding of the domain and refactoring as the understanding improved. That is not the same as knowingly writing bad code, which is the reading most people have absorbed.
- Top-left is a plan. It needs a trigger and an owner, and it is fine (Deliberate Debt).
- Top-right is a process finding. Repaying the code without changing what produced it just refills the register (Change Management).
- Bottom-left is the ordinary cost of learning, and the healthiest response is a small refactor while the understanding is fresh (The Refactoring Loop).
- Bottom-right is not a debt item, it is a capability gap, and paying it down without teaching produces the same code again next quarter (Knowledge Sharing).
| Prudent | Reckless | |
|---|---|---|
| Deliberate | "We must ship now and we will refactor once we understand the domain." A choice, with a trigger and an owner. The original metaphor, and a legitimate strategy (Deliberate Debt). | "We do not have time for design." A choice made without pricing the consequence. Common under sustained deadline pressure and usually a management problem rather than an engineering one. |
| Accidental | "Now that it works, we can see what we should have done." Unavoidable and often the sign of a healthy team learning its domain (Accidental Debt). | "What is layering?" A knowledge gap. The response is teaching and review, not a repayment plan — a register entry will not fix it (Review as Design Feedback — and Why It Arrives Too Late). |
Is this actually debt?
The forty-item list mostly dissolves under one question. What follows is not a way to dismiss engineering concerns — it is a way to give each concern the argument that will actually win it funding, instead of borrowing one that will not.
Does this choice make a change we will actually be asked for more expensive?
when The pricing rule lives in four modules and pricing changes monthly.
cost This is debt with a high interest rate. Price it in edits per change, attach it to the next funded piece of work in that area, and repay it there (Interest: Why Debt Compounds).
when No audit trail, a single-tenant id scheme, no timezone handling.
cost Debt with a long fuse. The expected-value case is weak and the retrofit cost is enormous, so argue it on risk rather than on frequency (Reversible and Irreversible Decisions).
when A gnarly parser nobody has touched in two years.
cost Not debt. If it needs to be understood, the answer is characterization tests and documentation, which is much cheaper than restructuring (Characterization Tests).
when "We use Redux and I would pick something else now."
cost Not debt unless it is making changes expensive today. This is a build-versus-buy decision to revisit with a trigger, not a repayment (What a Framework Charges).
when "We have no integration tests."
cost Not debt; it is unbuilt work. Call it that, because "we never built X" is a far easier thing to fund than "we have debt" (Testing as Design Feedback).
The things most often mislabelled
Nearly every long debt list is dominated by five things that are not debt. Each is a real concern with a real cost, and each has a better argument available than the one it is currently borrowing.
| Trigger | Symptom | Cause | Response |
|---|---|---|---|
| Code the current team did not write | "This whole module is debt" | Unfamiliarity read as defect; the module's behaviour is undocumented rather than wrong | Characterize the behaviour and write down what it does. Judge it afterwards (What "Legacy" Actually Means). |
| A framework or library the team would not choose today | "We need to migrate off it" | Preference and novelty, sometimes mixed with a genuine maintenance risk | Make it a decision with a revisit trigger: unsupported version, security advisory, a change it blocks (Build, Library, SaaS or Managed Service). |
| Missing tests | "Testing debt" | Work that was never done, not a choice that raised change cost | Fund it as risk reduction on the modules you are about to change (The Legacy Change Loop). |
| Inconsistent style | Long lists of formatting and naming items | Real friction, tiny cost, and highly visible, so it dominates lists | Automate it and stop discussing it (What to Automate Out of Review). |
| A design that is genuinely fine but unfashionable | "It is a transaction script, we should do DDD" | A pattern preference presented as an engineering finding | Ask what change it makes expensive. Usually none, and a transaction script is often correct (When Domain-Driven Design Does Not Pay). |
| A rule duplicated across tidy, well-named modules | Not on the list at all | It has no smell, so nobody flagged it | This is the expensive one. Find it by following requirements, not by reading code (Duplicate Knowledge). |
How to build it
Most important first.
- Define it narrowly and hold the line: a design or implementation choice that increases the cost of future change. Everything else on the list gets a different label and a different argument.
- For each item, write the change it makes expensive and how often that change arrives. Items where the change never arrives are not debt regardless of how the code looks.
- Separate the four quadrants — deliberate or accidental, prudent or reckless — because they need different responses and get confused constantly (Deliberate Debt, Accidental Debt).
- Attach every item to work that is already funded. Debt in an area you are about to change is cheap to repay; debt in an area you are not touching should stay in the register (The Refactoring Loop).
- Prune ruthlessly. An item that has sat in the register for a year without its change arriving is evidence that it was never debt (Revisit Triggers).
What the next change costs
The field this whole domain exists for. A structure is only better if it makes the change after this one cheaper — and it is worth saying which changes it does not help.
- With the loose definition, the next change costs whatever it costs, plus a recurring argument about a list that never shrinks, plus the credibility engineering loses each time a debt quarter produces nothing measurable.
- With the narrow definition, each item carries a stated cost — "every pricing change costs four edits instead of one" — so the next change has a price and the repayment has a payback period.
- The asymmetry that makes this worth doing: the loose list is prioritised by advocacy, and advocacy is uncorrelated with interest rate. The narrow list is prioritised by where change actually arrives.
- The narrow definition makes some real problems homeless. Poor naming, missing documentation and an unloved build are not debt by this definition and still cost real money; they need their own argument rather than a borrowed one.
- Requiring a named future change biases towards short horizons, and some of the most expensive debt has a long, uncertain fuse.
- Being strict about the word costs political capital in the short term, because "technical debt" is currently the only vocabulary the business already accepts.
What can go wrong
- The word becomes a rhetorical device — a way to make a preference unarguable — and the team stops being able to have the underlying design conversation at all.
- A "debt sprint" is funded, spends three weeks on the items that were easiest to describe, and changes nothing about the cost of the next feature.
- Real, expensive debt is invisible because it does not look bad: a perfectly tidy module that duplicates a business rule in four places has no smell and enormous interest (Duplicate Knowledge).
- The mitigation fails too: demanding that every item name a future change filters out debt whose change arrives rarely and catastrophically — an id scheme, a missing audit trail — which is exactly the debt worth paying early.
- The judgement depends on the expected change profile of the area, which is product knowledge and not code knowledge — so an engineering-only debt list is systematically wrong.
- Repaying debt depends on tests that would notice a behaviour change; without them repayment is itself a source of new risk (Refactoring Without Tests).
- "Technical debt means bad code." It means a choice that raises future change cost. Bad-looking code with one owner and good tests may have no debt at all; beautiful code that duplicates a rule in six places has plenty (What Makes Software Hard to Change).
- "All debt should be repaid." No. Debt in code that is stable, or scheduled for deletion, should be carried indefinitely — repaying it is spending to avoid a cost that will never arrive (When Design Does Not Pay).
- "Debt is always a mistake." The original metaphor is about a deliberate, prudent shortcut that lets you ship and learn. Taken knowingly and repaid on a trigger, it is a strategy (Deliberate Debt).
- "We can put a number on it." You can put a number on a specific change in a specific module and label it a model. A single figure for the codebase is not a measurement of anything, and publishing one converts a useful conversation into a target to be gamed.
- duplicate-knowledge
- god-object
- utility-dumping-ground
Testing it, and how it ages
- The test for whether something is debt is a thought experiment: name the next change in this area and count what it touches. If the count is unremarkable, the item is not debt (Change Amplification).
- Before repaying, ensure a failing test would catch a behaviour change in the affected area; that check is often the highest-value part of the work (Characterization Tests).
- After repaying, the claimed saving should be visible in the next real change. If nobody can point at it, the item was mislabelled and the register should record that.
- The metaphor has drifted since 1992 and will keep drifting; a team gets the benefit only by re-establishing the narrow meaning locally and using it consistently.
- Debt genuinely accumulates as the domain is understood better — the code encodes the old understanding, and that is Cunningham's original case rather than a failure (Requirements Are a Snapshot).
- A well-run register shrinks and its items get more expensive on average, because the cheap ones are repaid opportunistically and the remaining ones are the structural bets (The Debt Register).
Where this applies
This domain's advice is contested more than most. These labels say what each claim is specific to — and where CONTESTED appears, the note gives the strongest form of the opposing view rather than a caricature.
- GENERALThat some choices raise the cost of future change is true of any codebase in any language; what varies is which choices, since a dynamically typed codebase and a statically typed one accumulate different kinds.
- LIFETIME-SPECIFICFor code with a known end date — a campaign site, a migration harness, a prototype that will be discarded — there is no future change to make expensive, so there is no debt no matter what the code looks like. The concept only has meaning where the code must keep absorbing requirements.
- CONTESTEDA serious opposing position holds that the metaphor should be retired entirely: it invites a false quantification (principal, interest, repayment schedules) that no team can actually compute, and in practice it lets engineers avoid the harder conversation about what specifically will be expensive and why. Some experienced practitioners refuse the term and insist on naming the concrete cost instead. The counter is that the business already understands debt, and a shared imperfect vocabulary beats no vocabulary — but the criticism lands, and this module's narrowness is a response to it.
Where the depth lives
This domain teaches the codebase-level structure and hands the rest off.
- — Testing & Reliability Engineering — "we have no tests here" is the most common item on a debt list and is better argued as risk exposure than as debt, using the vocabulary that domain provides.