Technical Debt

The notion of technical debt is not well understood, so let’s start there.

Agile is all about fast feedback loops—hours or, at most, a couple days. Deliberately lengthening your feedback loop in pursuit of the chimera of perfection destroys your agility. The cost of that delay is a real cost. It usually exceeds development costs by a large factor. Deliberately delaying a release as you pursue perfect knowledge is IMO irresponsible. It’s literally so expensive that it can destroy the company.

That’s where the notion of “Technical Debt” comes from.

We deliberately release code in order to learn…
Tech debt is based on the very Agile idea that it’s a good thing to get working code into the user’s hands to get feedback, essential in fact. We deliberately release minimally functional code—no bells and whistles—in order to learn because that’s the best way to learn. We know it doesn’t do everything. That’s okay. By definition, then, the code we release will be imperfect. Not buggy. Imperfect. There’s a difference. We know as a fact that some of what our code does will be wrong; we just don’t know exactly what will be wrong. That’s tech debt.

Paradoxically, trading perfection for fast feedback usually gets us closer to perfection (or, at least, very good) faster. By going into debt and then quickly paying that debt off by learning what’s wrong through feedback and then acting on that feedback, we get to a better place in less time.

Tech debt accrues naturally as you write your code. Managed correctly, it works like a credit card. You pay off the card every month. If you don’t do that, the debit increases to the point that you can’t pay it off. You are buried under the interest payments. In Agile, the equivalent of paying off your card is refactoring to incorporate the things you learned by releasing. When you see something that’s wrong based on what you know now, fix it. If you don’t “pay off the debt”—don’t eliminate the hard-to-work-on code that doesn’t do what our users need—you’ll be buried under the “interest.”

The key notion is: you learn as you work. You simply can’t know everything you need to know in advance.
The key notion is: you learn as you work. You simply can’t know everything you need to know in advance. Debt accrues because you don’t know things. It falls into the “seemed like a good idea at the time” category.

Once you learn, you pay off the debt. To quote Ward Cunningham, who invented the notion of technical debt: “[the team changed the code] so it looked like they knew what they were doing to begin with.” (I strongly recommend watching Ward’s video on tech debt.)

Tech debt also accrues because we tend to learn only what’s needed to get the code to work. It is technical debt to learn that you’re not using a newly-learned library the way it’s supposed to be used. That’s not a bug—the code works as you expect, just not as well as it could. That’s another sort of learning problem. Fix it like any other technical debt.

Bugs are code that doesn’t behave the way you expect. Tech debt is code that behaves exactly as you expect, but your expectations are incorrect.
What about bugs? Bugs are code that doesn’t behave the way you expect. Tech debt is code that behaves exactly as you expect, but your expectations are incorrect. Tech debt has nothing to do with bugs. You deliver the best code that you can, given what you know now. You are not deliberately ignoring things about how the code should work that you do know. The released code is (or, at least, should be) high quality, fully tested, zero-known-defect code. It’s wrong, though, because you don’t know something, and there’s no way to know exactly how it’s wrong. You don’t know what you don’t know. You learn what’s wrong by releasing and getting feedback, and by writing more code and building experience.

Let me repeat this because it’s important. There shouldn’t be any known bugs at release and you should fix other bugs the instant they appear. Tech debt is not sloppy, badly written code. There is never an excuse for that. It slows you down to the point that agility is impossible. You cannot move fast enough if you can’t trust your code. The notion that you have to write sloppy code to get it out the door faster is a costly myth (to borrow a phrase from Deming). A plethora of bugs is not “tech debt.” It’s incompetence. It destroys programmer effectiveness.

Since bugs are really a separate topic from tech debt, I’ve discussed then in another post.

Whether or not to retire the debt is not always a straightforward decision. Sometimes, it makes sense to carry debt. (You don’t pay off your house loan a month after you get it.)

On the down side, not retiring your debt can bury the company. Nokia died under the weight of its tech debt (a technical architecture that wouldn’t let their developers make changes fast enough). Tech debt leads to things like build times measured in days. Agility is not possible in that sort of environment.

On the flip side of the coin, tech debt is not bugs. The code works. It’s high quality. It could be better, but maybe there’s something more important to do with your time than improving working code. You probably don’t want to put that off forever—changes take longer the more you delay them—but there may be no need to retire the debt immediately.

So, it’s okay to deliver, knowing full well that you’ll learn over time, and that you will need to refactor the code to leverage the things you learn. That’s basic Agile thinking. Technical debit is a good thing. It means that we’re learning.

Conversely, not refactoring—not paying off the debt—is an extreme form of dysfunction. It slows you down too much. That’s not Agile by any definition I know.


  1. Peter on December 16, 2019 at 11:04 am

    Can you give some examples of technical debt to sharpen your definition?

    • Allen Holub on December 16, 2019 at 5:17 pm

      Examples vary from trivial to huge. At the huge end, you may have an N-tier architecture that just doesn’t work in an Agile environment (google Conway’s Law) and the fix is going to a microservice architecture. At the smaller end, you may have gotten a detail of a story wrong, and when you sit down with your users to review the implementation, they either tell you or you see them struggle. Also included in the notion is something like code that works with a library to do something, and you’ve now learned that you’re not actually using the library properly and there’s a better way to do the same thing, so you refactor that code. It’s all about learning.

Leave a Comment