TL;DR: Technical debt is not a moral category, and "the code is ugly" is not an argument anyone funds. Describe each debt by its interest rate — what it costs every time the team ships, reviews, debugs, or onboards — put those costs in a table product people can read, and pay down at the moment just before the debt becomes a coordination tax, ideally while already touching the boundary.
Not all debt has the same rate
Ward Cunningham's original metaphor was precise: debt lets you ship sooner, and interest accrues until you pay the principal. What teams did with the metaphor was less precise — "technical debt" became a synonym for "code I dislike", which stripped the term of its one useful property: the idea of an interest rate.
Some debt is cheap and intentional — a hardcoded config in a service that will be rewritten next quarter charges almost nothing. Some debt charges interest on every single transaction the team performs: every release that waits on a manual step, every review that requires archaeology, every onboarding that takes weeks because the system's shape lives in three people's heads.
The question is never "is this debt?" — everything is, to some degree. The question is "what is the rate, and who is paying it?"
Treating debt as a moral failing produces two bad outcomes: engineers feel guilt over debt that is actually fine, and the genuinely expensive debt hides in the same undifferentiated pile as the harmless kind.
A working taxonomy
Fowler's quadrant — deliberate versus inadvertent, prudent versus reckless — is a good conversation opener, but for planning I use a blunter classification based on how the interest behaves:
- Fixed-rate debt. Costs the same small amount forever: an awkward but stable module nobody touches. Often not worth paying down at all — and admitting that out loud is liberating for a team.
- Escalating debt. Interest grows with the system: the shared database that was fine with one team and strangles four. The most dangerous class, because early cheapness disguises the trajectory.
- Compounding debt. Debt that generates more debt: missing tests make refactoring risky, so workarounds accumulate, which makes tests even harder to add. These loops deserve priority even when their current cost looks moderate.
- Callable debt. Low interest until an external event demands the principal at once: an EOL runtime, an expiring API version, a compliance deadline. Cheap until the date, then an emergency. Track the dates, not the discomfort.
Make cost visible
The reason debt discussions stall is that engineering describes the problem in engineering terms — coupling, duplication, "it's a mess" — and product hears nothing they can weigh against a feature. Cost of delay is the common currency. Describe every significant debt by what it does to delivery, and prioritization becomes a normal conversation:
| Debt | Interest (cost of delay) | Rate type | Paydown trigger |
|---|---|---|---|
| Manual release process | Every deploy waits on one person; releases avoided on Fridays | Escalating | Second team starts deploying weekly |
| Shared database between services | Every schema change coordinated across teams; delivery blocks | Escalating | Ownership split enters the roadmap |
| No contract tests on partner API | Integration regressions reach production monthly | Compounding | Partner announces monthly API changes |
| Legacy report module, stable | Ugly but untouched for a year | Fixed | Probably never — say so explicitly |
Notice the last row. A visible "we are deliberately not paying this" entry is as valuable as any paydown plan — it converts a vague background guilt into a decision that can be revisited when facts change.
Where the interest gets charged
When estimating a rate, I walk through the four places interest actually appears, because teams habitually count only the first:
- Delivery time. The obvious one — extra steps, waits, coordination overhead per change.
- Review quality. Debt-heavy areas get shallower reviews because understanding the context costs too much. Defects follow precisely where scrutiny fled.
- Incident behavior. Debt turns small incidents into large ones: no rollback path, tangled ownership, a recovery that requires the one person who understands the module.
- Onboarding and staffing. The system only three people can safely modify has a staffing risk priced in — it is just not on any dashboard until one of them resigns.
Choose the paydown moment
The best moment is usually just before the debt becomes a coordination tax — right before the second team starts depending on the boundary, before the traffic doubles, before the API partner accelerates. That timing requires watching trajectories rather than current pain, which is exactly what the rate-type classification is for.
The second-best moment is opportunistic: you are already touching the boundary for feature work, the context is loaded, and the marginal cost of doing it properly is at its lifetime minimum. The worst moment is the one teams most often choose — after the incident, in a guilt-driven cleanup sprint whose scope was set by embarrassment rather than by rates.
Paydown strategies
Three strategies cover nearly every case, and picking the wrong one wastes more effort than picking late:
- Alongside. Debt inside code you touch for features gets paid as part of that work — the boy-scout rule with a budget, typically ten to twenty percent of a change. Right for most fixed and low-escalating debt; it needs no permission and no ceremony.
- Campaign. Escalating or compounding debt whose paydown spans weeks needs a named, planned effort with a stated end condition — "all services deploy without manual steps" — not "improve CI". Right for the shared database, the release process, the test gap. A campaign without an end condition is a lifestyle.
- Strangle. When the module is beyond incremental repair, build the replacement alongside and route traffic over gradually. Expensive, slow, and the only honest option for some legacy cores — the mistake is choosing it for pride when a campaign would do.
The dedicated "tech debt sprint" every quarter is usually a symptom, not a solution — it means debt has no place in normal planning, so it gets a ghetto. Rates belong in the same backlog as features, argued in the same currency.
Talking to the business
Everything above collapses without one translation skill: describing debt in outcomes the business already cares about. "The checkout module has high coupling" gets a polite nod. "Every checkout change takes three weeks instead of one, and that ratio is worsening — here is the two-sprint fix that reverses it" gets a decision.
Three phrasing rules that have served me: lead with the delivery consequence, quantify the trend rather than the state (a worsening ratio alarms more honestly than a bad snapshot), and always attach the paydown cost — a named price makes it an investment decision instead of a complaint. And when the honest analysis says the debt is not worth paying down, say that too; credibility earned on the "no" cases funds the "yes" ones.
Summary
- Debt is not a moral category — ask for the interest rate, not the aesthetics.
- Classify by rate behavior: fixed, escalating, compounding, callable. The trajectory matters more than today's pain.
- Publish a cost table in cost-of-delay terms, including explicit "deliberately not paying" rows.
- Count interest in all four ledgers: delivery, review quality, incidents, onboarding.
- Pay down just before the coordination tax hits, or opportunistically while touching the boundary.
- Match strategy to debt: alongside, campaign with an end condition, or strangle — and keep rates in the normal backlog.