Technical Debt Management
The "technical debt" metaphor is the most-borrowed concept in software engineering and one of the most misused. The original Cunningham definition was specific: shipping code that you know is not the long-term right design, deliberately, for short-term speed. The current usage has expanded to mean "code I don't like" or "code I would write differently now," which dilutes the framework and makes priorities harder.
This page is about distinguishing real debt from preference, the kinds that compound vs. the kinds that don't, and the practical work of managing it.
What technical debt actually is
The useful definition: a deliberate or inherited choice that increases the cost of future change. Specifically:
- **Deliberate**: shipped knowingly with a plan to revisit (or accepting we won't)
- **Increases the cost of future change**: not just code that exists, but code that *makes the next change harder*
Code can be old, ugly, or unfamiliar without being debt. Debt is what makes the next thing slow.
The kinds of debt
Different categories behave differently — they compound at different rates and respond to different repayment strategies.
Implementation debt
The mechanism is suboptimal but works. A handwritten cache that should be Redis. A spreadsheet-driven workflow that should be automated. The functionality is correct; the implementation is brittle or inefficient.
**Compounding rate**: low. Implementation debt that works tends to keep working.
**When to repay**: when the implementation actively hurts (downtime, cost) or when a related change makes the work cheap.
Design debt
The structure is wrong for the current requirements. A monolith that should be a few services, or vice versa. Coupling between modules that should be independent. Boundaries that no longer match the problem.
**Compounding rate**: high. Each new feature has to navigate the wrong structure; the cost of new work grows over time.
**When to repay**: when feature velocity has noticeably slowed, when the structure is preventing changes that have business value.
Knowledge debt
The team that knows the system has shrunk. Documentation is sparse. The original engineers have left. Specific decisions cannot be explained.
**Compounding rate**: high. Each successive engineer has less context; mistakes happen because the constraints are forgotten.
**When to repay**: continuously. Document decisions as they happen. Train new people on existing systems before the original team is gone.
Test debt
Test coverage is sparse or unreliable. CI catches some bugs but misses many. Refactoring is risky because behavior is not pinned down.
**Compounding rate**: medium-high. Each ungated change has the potential to introduce regressions; the codebase becomes harder to evolve.
**When to repay**: when bug rate or regression rate is unacceptable, or before any major refactoring.
Dependency debt
Libraries, frameworks, and runtime versions are outdated. Security patches require upgrading; breaking changes have accumulated.
**Compounding rate**: low until it becomes catastrophic. The longer dependencies sit, the harder the upgrade.
**When to repay**: continuously, in small steps. Big-bang version upgrades are notoriously painful; small steady updates are tractable.
What is not technical debt
A common misuse: calling something debt because it does not match current preference.
- **Code in an old style** is not debt unless it makes change harder
- **Code by other people** is not debt
- **Code that is not what you would write today** is not debt unless it actively impedes change
- **Working code that solves a problem you do not understand** is rarely debt; understand it first
- **Code with comments you find boring** is not debt
Real debt produces measurable friction. If there is no friction, there is no debt.
The cost of carrying
Different debts have different ongoing costs:
| Debt type | Direct cost | Indirect cost |
|-----------|-------------|---------------|
| Implementation | Operational (perf, cost) | Lower |
| Design | Slower feature delivery | Compounds |
| Knowledge | Mistakes in future changes | Compounds |
| Test | Regressions | Compounds |
| Dependency | Security risk | Bumps to catastrophic |
The compounding categories are where most of the real cost lives. A team carrying significant design or knowledge debt sees its delivery rate drop over time without any single visible cause.
Triage: what to repay first
The honest framework:
1. **Security debt** (vulnerable dependencies, known issues) — repay immediately
2. **Debt blocking high-value work** — if a planned feature is significantly harder because of debt, repay as part of feature
3. **Debt with active blast radius** — bugs being caused, customers affected, on-call woken
4. **Compounding debt with clear repayment path** — design debt where you know how to fix it
5. **Slow-burn debt** — old code, suboptimal implementation that works fine
Most teams repay too much of category 5 (cosmetic) and not enough of category 1–3 (urgent and high-value).
The repayment patterns
Inline repayment
Pay the small debt as you make changes nearby. The "boy scout rule" — leave the campsite cleaner than you found it. Works for small fixes; doesn't scale to design debt.
Project-based repayment
A planned project to repay specific debt — refactor a module, upgrade a framework, document a system. Works for medium-scale debt with a clear endpoint.
Strategic refactoring
Major design changes that take significant time. Use the [strangler pattern](RefactoringStrategies) and [expand-and-contract](RefactoringStrategies). Plan for months; check in regularly.
Decommissioning
Sometimes the right answer is to remove the debt entirely — retire the system, delete the unused code path, kill the feature. The "we'll fix it eventually" promise often lives longer than the system itself.
Anti-patterns
- **Treating all debt as equal.** Different debts have different costs. Triage matters.
- **"Big rewrite" as the response to design debt.** The original problem usually returns in the rewrite at higher cost.
- **Cosmetic refactoring as "debt repayment."** Renaming variables doesn't move the needle on debt that compounds.
- **Pretending debt doesn't exist.** "We'll fix it later" without a specific plan rarely happens.
- **Pretending debt is everything.** "We need to stop features and fix debt" without specifying which debt is too vague to act on.
Communicating about debt
Engineers are often bad at explaining debt to non-engineers. The translations that work:
- "X is making feature Y harder" (specific cost)
- "We're losing N hours per week to Z" (quantified)
- "The next change in this area will take 2x longer because of debt" (predictive)
- "Fixing this debt makes the planned roadmap take 3 months instead of 6" (business framing)
Generic "we have a lot of technical debt" is rarely persuasive without specifics.
Common failure patterns
- **Confusing preference with debt.** Ages-old code in an unfamiliar style is not always debt.
- **Repaying debt that does not compound.** Spending time on cosmetic cleanup while design debt compounds.
- **Big-bang debt repayment.** Often fails; incremental works.
- **No measurement.** "We have less debt" is meaningless without a metric or felt change.
- **Endless prevention without repayment.** Perfect new code does not fix existing debt.
Further Reading
- [RefactoringStrategies](RefactoringStrategies) — Specific repayment techniques
- [LegacyCodeModernization](LegacyCodeModernization) — Inherited debt scenarios
- [CleanCodePrinciples](CleanCodePrinciples) — Avoiding new debt
- [EngineeringDecisionFrameworks](EngineeringDecisionFrameworks) — How to communicate trade-offs
- [SoftwareEngineeringPractices Hub](SoftwareEngineeringPracticesHub) — Cluster index