A system has technical debt when the effort needed to keep it fit for purpose is surprisingly high. Zero technical debt means that according to our current knowledge, cost of foreseeable future work and maintenance is the lowest possible.
Consequences:
If the system will not be changed and does not need active maintenance, it has no tech debt.
Tech debt is not ‘absolute’, it depends on how the system is going to be changed in the future.
The cause of tech debt is not just (or maybe not even primarily) ‘bad code’, but lack of clear product vision. Keeping tech debt low without stable product direction would require code that is extremely easy to change in many ways, and this kind of code is expensive to write.
Often when someone who does not know the internal implementation but knows the specification and behavior of a system (‘outsider’) is asked to estimate how much time is required to change the system to meet a new requirement, then that person assumes that the system they are dealing with has zero technical debt.
Expert outsiders tend to assume that some technical debt exists, and change their estimates accordingly. A system has above average technical debt when expert outsiders seriously underestimate the level of technical debt, i.e. when they seriously underestimate the effort required to maintain and make changes to the system.
Advice to engineers:
Do not refer to tech debt as ‘bad code’. It is almost never caused by programmers making mistakes on their own.
Developers who wrote code that’s considered to have ‘technical debt’ today were likely not incompetent, just they had different information back then.
If tech debt is crippling development, first find out what the root causes are.
Spaghetti code means you need better coding guidelines, code reviews, trainings.
Constantly changing business requirements need to be addressed by more transparent, stable product strategy, as code is always written with some assumptions about the future and if assumptions change you end up with tech debt.
In case the code has originally been written with a different product vision in mind and the current, different vision requires lots of workarounds, then slow down, make the new vision fully transparent to the team and consider a major refactoring or reimplementation.