Hasan's Journal

Stories, lessons, and scars from production.

Mehedi Hasan
Back to blog

Technical Debt: A Framework for Decision-Making

Not all technical debt is bad. The trick is knowing which debt to take on, which to pay down, and which to leave alone. Here's the framework I use.

#Technical Debt#Architecture#Leadership

The Metaphor and Its Limits

The "technical debt" metaphor is useful because it frames a technical concept in financial terms that business stakeholders understand. Debt is something you take on deliberately, with an expectation of paying it back, and the cost of carrying it (interest) is the price of having the money (or the speed) now rather than later. That framing is valuable because it moves the conversation from "should we write clean code?" (a moral framing that business stakeholders don't engage with) to "is this debt worth taking on?" (a financial framing that they do). The metaphor has limits, though: financial debt has a clear interest rate and repayment schedule, while technical debt has a fuzzy cost that's hard to quantify and a repayment schedule that's entirely discretionary. The fuzziness is why technical debt tends to accumulate — there's no bill that arrives every month reminding you to pay it down.

The other limit of the metaphor is that it treats all debt as the same kind of thing. Financial debt is homogeneous — a dollar borrowed is a dollar owed — but technical debt comes in different flavors with different characteristics. Some debt is strategic and should be celebrated, not regretted. Some debt is accidental and should be fixed as soon as you notice it. Some debt is inevitable and should be managed rather than eliminated. Treating all debt the same leads to either paralysis (trying to eliminate all debt, which is impossible and would halt all feature development) or recklessness (ignoring all debt, which eventually makes the system unmaintainable). The framework is about distinguishing between the flavors and responding appropriately to each.

The Four Types of Debt

The first type is strategic debt — debt you take on deliberately to ship faster, knowing you'll pay it back later. Shipping a feature with a hardcoded configuration instead of a proper settings UI is strategic debt: you're trading future flexibility for current speed, and the trade-off is explicit. Strategic debt is healthy when the speed matters more than the flexibility at the moment of the decision, and when you actually pay it back before it compounds. It becomes unhealthy when you don't pay it back, because then it transforms into the second type: accidental debt. Accidental debt is debt you didn't know you were taking on — a design that seemed right at the time but turned out to be wrong once you learned more. Accidental debt is unavoidable in software development, because you never have complete information at the time of the decision, and the right response is to fix it when you discover it rather than to regret it.

The third type is inevitable debt — debt that comes from the gap between your current architecture and the architecture you'd build if you were starting from scratch today. Every system has this, because technology and requirements change faster than systems can be rebuilt. Inevitable debt is neither good nor bad; it's a fact of working with long-lived systems, and the right response is to manage it — to be aware of it, to plan for eventual migration, and to avoid building new things on top of the deprecated parts. The fourth type is reckless debt — debt taken on with no plan to pay it back, often under time pressure that feels exceptional but is actually chronic. Reckless debt is the dangerous kind, because it compounds silently and eventually reaches a tipping point where the system becomes unmaintainable. The difference between strategic and reckless debt isn't the debt itself — it's whether there's a plan and an intention to pay it back.

Paying It Down

The practical question is how to pay down debt in a way that's sustainable alongside feature development. The answer I've found works is to budget for it explicitly — a percentage of every sprint devoted to debt paydown, not as a "if we have time" activity but as a committed allocation. Twenty percent is a number that works for most teams: enough to make real progress on debt without crippling feature velocity, and enough that the debt doesn't grow faster than you can pay it down. The specific work changes every sprint, but the allocation stays constant, which creates a steady pressure against debt accumulation without requiring a special "debt paydown sprint" that disrupts the normal cadence.

The other principle is to pay down debt opportunistically — when you're in a part of the codebase for a feature, fix the debt you encounter while you're there. "Leave the codebase better than you found it" is a simple rule that, followed consistently, prevents debt from accumulating in the areas that are actively being developed. The rule doesn't apply to the parts of the codebase that aren't being touched, which is where the explicit twenty-percent allocation comes in, but it does prevent the actively-developed parts from degrading. The combination — opportunistic paydown in actively-developed areas plus explicit allocation for the rest — is the most sustainable approach I've found, and it's the approach that has kept our codebase maintainable over years of development without ever requiring a major rewrite.