Ward Cunningham introduced the technical debt metaphor to explain to non-technical stakeholders why code needed to be refactored. The analogy to financial debt was deliberate: sometimes you borrow money to move faster, and the interest compounds over time until the debt needs to be paid.
Over time, the metaphor has been stretched to cover every imperfect thing in a codebase. This is not useful. Not all debt is the same. Not all debt should be paid down.
The Two Kinds of Debt
Intentional debt (Cunningham’s original meaning): a conscious decision to take a shortcut now to deliver faster, with the explicit understanding that it creates future work.
“We’ll hardcode this configuration for the v1 launch. We know we need to make it configurable; we’ll do that for v2.”
This is legitimate. It’s a trade-off with clear eyes. The debt is documented, bounded, and has a plan.
Accidental debt (the other 80% of discussions): code that wasn’t designed well, that accumulated complexity over time, that nobody intended to create but nobody addressed.
“Nobody’s sure how this module works. We’re afraid to change it. Tests keep failing in ways we can’t explain.”
This is the debt that has a real carrying cost.
The distinction matters because the appropriate response is different. Intentional debt has a resolution plan. Accidental debt needs to be assessed for whether it’s worth fixing.
Architectural Debt vs Code Debt
Code-level debt (ugly methods, missing abstractions, inconsistent naming) is visible and annoying but usually manageable. Most code debt doesn’t actively block work — it just makes things slower.
Architectural debt is the expensive kind. Decisions made about system structure — module boundaries, data ownership, communication patterns, integration points — are hard to change and get harder over time as more code builds on top of them.
Examples:
- A modular system where two modules have merged over time due to shared database access
- A service that owns data it shouldn’t, creating dependency chains across the system
- An event schema that encodes internal state that other services now depend on
- An API that returns more data than consumers need, creating implicit coupling
These don’t look like bugs. They look like “how the system works.” But they constrain what changes are possible, and they get more constraining as the system grows.
Architectural debt is worth the investment to address. Code debt may or may not be.
Assessing Debt Worth Fixing
Not all technical debt should be paid down. The relevant questions:
Does this debt block something important? If a messy module is never touched and causes no incidents, it may not be worth the investment to clean it up. “Code doesn’t have to be pretty; it has to be maintainable enough.”
What is the carrying cost? Debt that slows down every feature change in a high-velocity area is expensive. Debt in a stable module nobody changes has low carrying cost.
What is the fix cost? Some debt is easy to fix and the investment is obviously worth it. Some debt would require a 6-month migration and the business benefit is unclear.
What is the risk? Touching a poorly-understood, poorly-tested module to clean it up introduces its own risk. Sometimes the right answer is “leave it alone, wrap it with a clean interface, and build the new stuff on top.”
The Carrying Cost Model
A useful mental model: technical debt has a principal (the work required to fix it) and an interest (the slowdown it causes per unit of work in the affected area).
High-frequency area + high complexity = high-interest debt. This is worth paying down. Low-frequency area + moderate complexity = low-interest debt. This can wait or be tolerated indefinitely.
Debt priority = (features/quarter affected) × (slowdown factor) / (fix cost)
This isn’t a precise formula. It’s a frame for having the conversation: “This debt slows down our most important feature area by maybe 20%, it would take one sprint to fix, and we ship five features per quarter in this area. It’s worth the investment.”
Making the Case for Technical Investment
Technical debt work competes with feature work for engineering time. The people making resourcing decisions usually care about features.
The conversation that doesn’t work: “Our code quality is poor and we need to refactor.”
The conversation that sometimes works: “We’re spending 30% of every sprint fighting this legacy module. That’s 1.5 engineers per quarter. A 3-sprint investment to replace it pays back in 6 months and reduces our incident rate in this area.”
This requires measurement. If you can’t quantify the cost of the debt, you can’t make a compelling case for fixing it. Track:
- Time spent on work in affected areas vs estimates
- Incident rate in areas with high debt concentration
- Onboarding time for new engineers in complex modules
- Failed deployments attributable to fragile areas
With data, the conversation is possible. Without it, you’re asking for time to make code “nicer” — which doesn’t compete well with features.
Refactoring as Normal Engineering Work
The most successful teams don’t have “refactoring sprints” or “tech debt quarters.” They refactor as part of normal feature work — the Boy Scout Rule: leave the code better than you found it.
This works when:
- Engineers are given time estimates that include reasonable cleanup
- The team has a shared definition of acceptable code quality
- Refactoring is visible in reviews and celebrated, not treated as extra
- Product/design understand that some velocity reduction now prevents larger velocity reduction later
It doesn’t work when:
- Every estimate is stripped of slack in planning
- “Refactoring” is always deprioritized in favor of features
- Engineers aren’t empowered to push back on accumulating debt
Accepting Some Debt Permanently
Not all technical debt needs to be fixed. Legacy systems that are being sunset, modules that are frozen by stability requirements, code that “works and we don’t touch it” — these can carry debt indefinitely if the carrying cost is genuinely low.
The problem is when teams accept all debt permanently, accumulating it until the system is unmaintainable. The discipline is distinguishing between “this debt has acceptable carrying cost and we’re consciously accepting it” and “this debt is too expensive but we’re procrastinating on it.”
Write it down. An ADR that says “we know this is not ideal and here’s why we’re not fixing it” is better than unexplained technical debt that future engineers assume was important.
The Leadership Responsibility
Technical debt is a leadership problem as much as an engineering problem. It accumulates when:
- Deadline pressure consistently overrides quality
- There’s no mechanism for engineers to surface debt visibility
- Refactoring work has no way to compete with features in planning
Engineering leaders who treat technical debt as a purely technical concern are missing half the problem. The debt is technical; the causes and solutions are organizational.
The goal is not zero debt. It’s managed debt — where the team understands what debt it carries, why, and has a process for deciding when it’s worth paying down.