A few years ago, a hiring manager asked me what my definition of tech debt was. I gave what I thought was a perfect answer, the hiring manager nodded and smiled, jotted a few notes and moved on. I have since learned that I was dead wrong.
My answer at the time was, "tech debt is the inevitable result of building and shipping software quickly. It's the result of the tradeoffs we have to make in order to ensure we get features in front of users as soon as possible."
While I still feel getting features in front of users and soon as possible is important. The idea that this inevitably results in a sacrifice of code quality, flexibility, or maintainability is not the case.
Since then, I saw a video by the person that coined the term "tech debt," Ward Cunningham. In it he describes the analogy in detail but it is clear that tech debt is NOT writing shitty, untested, low-quality, or slipshod software to ship something faster.
In the pursuit of getting an idea in front of users as soon as possible, we often begin writing our software with an incomplete understanding of the problem being solved. There will be both known unknowns and unknown unknowns along the way.
Solving business problems with software is a journey. While we do as much planning as possible up front, at some point, those UML diagrams and story maps have to get turned into code. In short, Frodo can't just keep drawing maps to Mordor, he's got to take a first step.
As we build and learn from both the construction process and from early users, the problem itself becomes more clear and we see ways to refactor the code and make it better. For example we see the need to split a module or maybe to combine two based on new information.
Refactoring said modules are much easier when they are well written and well tested. The tech debt being paid back so to speak is improving the code base based on a new understanding of the problem.
Now imagine going into the code discovering not only do you have to combine or split those modules, but you have to add test coverage because the developer that wrote them had no time for tests. Maybe they even left a comment apologizing for the "tech debt"...
This is an example of what tech debt is not. We may not have landed on the perfect architecture of module structure for the problem, but the code itself should be of sound quality. So what is my new answer to that question?
Tech debt is the inevitable result of writing GOOD software with the understanding of a problem you have today. We pay tech debt down through refactoring as our understanding of the solution grows.
Tech debt is NOT the result of writing software quickly, without regard for the future, to meet a deadline or ship faster. This is called cutting corners, and typically this results in trash code that must be thrown out rather than refactored.
So this leads to the last important characteristic of tech debt, which is that it can be paid down through refactoring, whilst shitty code seldom can.