← All posts

Technical Debt Is a Budget Problem, Not a Code Problem

The decision to take on technical debt is a resource allocation call, not a lapse in discipline — and treating it like one leads to worse outcomes

The phrase "technical debt" was coined by Ward Cunningham in 1992, and it's been misused ever since. The metaphor is a financial one — debt, interest, repayment — but in practice, teams use it as a polite way to describe code they're embarrassed by. That framing leads them to manage it wrong.

Debt isn't inherently bad. Every business uses it. The question is whether the terms are favorable and whether you're borrowing with intention. The same logic applies to software.

The Decision You're Actually Making

When you skip writing tests for a feature, defer refactoring a messy module, or ship a workaround instead of a proper solution, you're making a resource allocation decision. You're saying: "The value of shipping this now outweighs the cost of doing it the long way." That's a budget call.

The problem isn't that developers make this call. It's that they often make it without acknowledging it's a call at all. The debt gets taken on implicitly, invisibly, without anyone logging the liability. No one budgets for the repayment. The interest compounds.

In a financial context, borrowing $50K for a year at 10% and forgetting you borrowed it would be catastrophic. Yet teams do the software equivalent constantly — ship the shortcut, move on, and treat the resulting slowdown as mysterious bad luck.

Two Kinds of Debt

There's a meaningful distinction between intentional and accidental technical debt, and conflating them causes real damage.

Intentional debt is a deliberate trade. You know what you're skipping, you understand the likely consequences, and you decide the near-term gain is worth it. This is entirely rational. A startup needs to prove product-market fit before investing in infrastructure. An agency needs to hit a launch date. A solo developer needs to ship a feature this week to keep a client engaged. When you make this trade consciously, you can plan for repayment — you know what you'll need to fix later, and you can schedule it.

Accidental debt is what happens when no one was paying attention. A class gets modified one piece at a time over two years until no one understands its full behavior. An API endpoint accumulates six different callers with slightly different expectations. A codebase gains three different ways to handle authentication because each one was added without reviewing what was already there. This debt is harder to repay because the first step — understanding what you owe — takes real work.

The goal isn't to eliminate intentional debt. It's to eliminate accidental debt, and to manage intentional debt like an actual ledger.

What the Interest Rate Looks Like

When technical debt compounds unchecked, the symptom isn't a sudden crash. It's a gradual increase in friction. Features that used to take three days start taking two weeks. Bugs in one area cause regressions in something completely unrelated. Developers spend a growing percentage of their time context-switching into old code just to understand it before they can change it.

This is the interest payment. And unlike financial interest, it's invisible on any balance sheet. It shows up as missed timelines, burned-out engineers, and clients wondering why simple things take so long.

I've seen this pattern across different types of projects. In specialized industries where regulatory requirements shape software decisions, teams often take on heavy intentional debt during launch — skipping validation layers, hardcoding configurations that will need to be dynamic, shipping with minimal error handling — because the product has to exist by a certain date. That's defensible. What's not defensible is treating that debt as permanent because the launch pressure never let up.

Tracking It Like a Budget

The single most useful thing a team can do is treat technical debt as a line item. Not metaphorically — literally.

When you make a shortcut, write it down. Not in a FIXME comment buried in the code, but somewhere that gets reviewed. What did you skip? What are the downstream risks? When do you plan to address it? What will it cost approximately?

This serves several purposes. It forces honest accounting at the moment of the decision, rather than retroactively. It makes the accumulated liability visible to non-technical stakeholders, who can then make informed prioritization decisions. And it creates a repayment queue that can be worked through systematically.

On a recent project I work on regularly, I maintain a running list of known limitations — things that were shipped intentionally incomplete, edge cases that aren't handled, architectural decisions made under time pressure that I intend to revisit. The list has an entry for each item that notes what the risk is and roughly when it should be addressed. It's not elaborate. A plain document works fine. But having it means I'm never surprised by a debt I forgot I took on.

The Refactor That Pays for Itself

There's another piece to this that gets missed in the "technical debt is bad" framing: some refactors pay for themselves immediately.

Not all debt is equivalent. A messy utility function that nobody calls very often is basically benign. A core data model that every feature in the product depends on is a different story — the interest rate on debt there is punishing because every new feature has to navigate it.

When you're budgeting for repayment, the question isn't "what's the messiest code?" It's "where is the mess costing us the most?" Paying down debt in high-traffic areas of the codebase — the code touched by every deploy, every new requirement, every new hire trying to get up to speed — returns value disproportionate to the effort.

This requires actually mapping your codebase's load-bearing areas. Most teams have a rough intuition about where the bodies are buried. That intuition is worth making explicit.

What This Means for Project Conversations

If you're a developer talking to a client or stakeholder about technical debt, the financial framing is genuinely useful for getting through. "We borrowed against future velocity to ship this feature on schedule" is a sentence that non-technical people can process. "We have some code that needs refactoring" is not.

Being honest about the debt you've taken on — and what the repayment plan is — builds more trust than pretending the shortcuts don't exist. Clients who understand that software development involves trade-offs are better partners than clients who believe a delivered feature is unconditionally finished.

The goal isn't to hand someone a list of everything wrong with the code. It's to communicate that you're managing it deliberately, with an eye toward the long-term health of the thing you're both investing in.

Technical debt isn't a character flaw. It's a financial instrument. Use it when it makes sense, track what you owe, and pay it back on a schedule — before the interest makes everything else harder than it needs to be.