Technical debt is a useful shorthand. It's also why nothing gets fixed.
Ward Cunningham's original term described one thing precisely. Four different problems get called 'technical debt' today — and each needs a different fix.
The meeting usually goes like this. An engineer says the codebase has accumulated significant technical debt. Someone asks how bad it is. The engineer says pretty bad. Everyone agrees something should be done. Nothing gets prioritised. Three months later, the same meeting happens again.
This pattern is so common it has become a kind of engineering folklore. The reason the meeting goes nowhere isn't that organisations don't care about code quality. It's that 'technical debt' now describes four completely different engineering problems. When you can't name the actual problem, you can't have the right conversation about addressing it.
What Cunningham actually meant
Ward Cunningham coined the term in 1992 to describe one specific situation: choosing a quick implementation deliberately, with the intention of revisiting it once the design became clearer. His framing was precise. Like financial debt, you borrow against future time consciously, with a plan to repay. The interest accrues if the repayment doesn't happen.
That definition covers one scenario. The engineering world has since stretched 'technical debt' to cover outdated NPM dependencies, 4,000-line services that nobody fully understands, data models that made sense in 2018 and now don't, and codebases that have simply grown complex from feature accumulation. These are not the same problem. They don't require the same fix. And they don't warrant the same conversation with stakeholders.
Four things engineers call 'technical debt'
1. Deliberate shortcuts — actual debt
A deadline is approaching. The proper data model would take two weeks to build; a workaround would take two days. The team takes the workaround, creates a ticket to fix it properly after launch, and ships. This is Cunningham's definition exactly: the trade-off was conscious, bounded, and came with a repayment plan.
What goes wrong: the repayment ticket drifts through the backlog for eight months, deprioritised each sprint. The interest doesn't accrue as a financial rate. It accrues as every future feature that has to work around the workaround, and every engineer who spends thirty minutes understanding why the code does something odd.
The right question: which sprint does the repayment go into, and what gets displaced to make room for it?
2. Dependency decay — a maintenance backlog
A library you depend on released four major versions since you integrated it. Your runtime is two LTS cycles behind. The test framework your team adopted in 2020 is now unmaintained. Nobody made a bad decision. Nobody took a shortcut. The ecosystem moved on while the codebase held still.
This is not debt. Nobody borrowed anything. It's the normal consequence of software existing in a moving ecosystem. Framing it as debt implies someone made a choice they owe the team for. They didn't. It also produces the wrong framing: 'paying back' dependency decay implies a one-time sprint. What's actually needed is an ongoing maintenance budget.
The right question: what's the security risk exposure from running behind on patches, and what does a maintenance budget look like to keep dependencies reasonably current?
3. Accumulated complexity — architectural drift
A module that started as 200 lines is now 4,000. A service that does eleven things. A data model with thirty tables because each new feature added a table rather than extending the schema. No single decision was wrong. Complexity compounded from feature additions, the way dust accumulates, imperceptibly, until it obviously is a problem.
There is no 'repayment' for accumulated complexity. There's a refactor, a redesign, or an accepted maintenance cost. The question isn't 'when do we pay this back' but rather: 'what does this complexity cost us per quarter in slower onboarding, higher regression rates, and longer debug cycles — and does a redesign deliver a better return than those costs?'
The right question: what's the measurable cost of the current complexity: regression rate, onboarding time, debug cycles. What would a bounded cleanup deliver against that?
4. Wrong domain model — the learning cost
The code correctly implements the wrong understanding of the problem. Three years ago, the team built the permission system around user roles because the requirements said to. The business has since evolved to need attribute-based permissions that roles can't represent cleanly. The code isn't wrong. It's faithful to the specification it was built from. The specification was the limiting factor.
This is the cost of learning a domain, and every sufficiently long-lived software product carries it. The original model was the right model given the domain understanding at the time. Calling it debt implies an engineering mistake was made. The limiting factor, if there was one, was insufficient domain exploration before the first implementation — which is a product and process conversation, not an engineering failure.
The right question: has our domain understanding changed enough to justify a model redesign, and what's the migration cost compared to the ongoing friction of the current model?
| Category | Cunningham's 'debt'? | What it actually is | The conversation to have |
|---|---|---|---|
| Deliberate shortcut | Yes | Borrowed time with a repayment plan | When does the repayment sprint go in? |
| Dependency decay | No | Maintenance backlog | What's the security risk and ongoing budget? |
| Accumulated complexity | No | Architectural drift | What does complexity cost per quarter? |
| Wrong domain model | No | Domain learning cost | Is the model redesign worth the migration? |
Why the vocabulary change matters in practice
None of these categories are trivial to address. Getting specific about which one you're dealing with isn't pedantry. It changes what stakeholders can say yes or no to.
When an engineering team enters a planning meeting with 'we need to address technical debt', leadership typically hears one of two things: 'the engineers made a mess and want time to clean it up', or 'the engineers want capacity for things nobody outside the team will notice'. Neither framing tends to produce prioritised engineering time. Both invite indefinite deferral.
“Vague language protects the asker from a clear no. It also protects the decision-maker from making a clear decision.”
Precision works differently. 'We're running Node 16 in an environment where the current LTS is Node 22, which means our security scanner can't update and our dependency tree has seventeen CVEs with no patch path' is a risk conversation. It has scope, severity, and a business case. Someone can make a decision.
'The payment module has more conditional logic than any single engineer fully holds at once, and our regression rate there is 3× higher than in any comparable module' is a reliability conversation. It has data. Someone can decide whether a refactor is worth the regression-rate improvement.
These conversations can still result in 'not this quarter'. But 'not this quarter' is a decision. It creates a trigger condition for the next conversation. 'We'll get to the technical debt backlog eventually' is not a decision — it's a deferral with no trigger condition, which is functionally indistinguishable from 'never'.
What to say instead
The next time someone raises technical debt in a planning meeting, ask which of the four categories it belongs to. Is it a deliberate shortcut that was supposed to be revisited? Dependencies that have fallen behind? Complexity that is measurably slowing the team? Or a domain model that no longer fits the way the business works?
The question is slightly awkward the first time. It tends to produce a more specific answer. That's the point. The specific answer is the one that can be scoped, scheduled, and evaluated against competing priorities.
Cunningham's debt metaphor was useful when it described one thing precisely. It became less useful as it expanded to describe everything. Getting specific again is how the conversation finally goes somewhere.
Frequently asked questions
Related reading
Most AI strategy decks are written backwards
AI strategy decks that list capabilities by department feel comprehensive and systematically land on the wrong priorities. The fix is not a better use-case inventory — it is a constraints map.
Hiring senior engineers in a market that’s split in two
General software engineering roles are down ~36-49% while AI/ML openings are up 59%. The result is two candidate pools with almost no overlap — and most job descriptions accidentally fish in both.
Write a postmortem that someone outside your team will actually read
Most postmortems document everything and change nothing. The failure is in the document design, not the analysis. Here is the structure that works for the three distinct readers every incident review actually has.