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:

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.

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 typeDirect costIndirect cost
ImplementationOperational (perf, cost)Lower
DesignSlower feature deliveryCompounds
KnowledgeMistakes in future changesCompounds
TestRegressionsCompounds
DependencySecurity riskBumps 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 and expand-and-contract. 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

Communicating about debt

Engineers are often bad at explaining debt to non-engineers. The translations that work:

Generic "we have a lot of technical debt" is rarely persuasive without specifics.

Common failure patterns

Further Reading