Technical debt is a useful term in product development and software engineering, and has become a staple of our everyday discussions. The idea that a certain type of ‘debt’ accumulates as you build a product or write software is a great analogy that helps communication between engineers and non-engineers in the teams we work in. The strength of that analogy helps with the prioritisation of paying back that debt vs continously deprioritizing it against newer features, and conversely also helps us envision the idea that you can strategically ‘leverage’ choice pieces of debt by defering perfect design choices in order to achieve a goal, whilst agreeing that those short-term decisions will need to ‘paid off’ later.
However, over the years the prefix ‘technical’ hasn’t sat well with me. The main reason being that the term implies that the ownership of producing and tackling this debt lies wholly with the engineering team. This has negative consequences in my mind because this can lead to destructive cultural situations such as engineering having to pitch for resource to address the debt vs building towards a product roadmap, or an engineer making product decisions when refactoring a feature to improve the design without input from product decision makers.
I’ve experimented over the last few years in trying to reclassify and break down the current image ‘technical debt’ into a few more useful and informative terms to address these issues, and so far I’ve found them useful enough that in this post I want to share them, not only for use retrospectively to tackle to debt, but also looking forward to try and minimise it.
1) Product / Feature Debt
There are two main scenarios I would use this term for.
The first is areas of a product that are not sub-optiomal due to code health or technical performance, but are sub-optimal given changes in company direction or market, and are now no longer suitable to purpose. Some of this will be obvious to people outside of engineering, but often underlying issues are opaque to those not looking at the code, and those looking at the code can’t make the decisions alone when tackling it.
The second scenario is somewhat linked, and what I define as accidental complexity in the product experience. Accidental vs essential complexity is normally considered from a technical point of view, but I think it is just as relevant in product and design as well. Developers will treat the definition of the domain we are tasked to build as ‘essential’ and do their best to not introduce accidental complexity when modelling it, but the more complex the ‘essential’ is, the more complex the solution has to be, and can end up unwieldy and cognitively taxing to work with, and I would classify that as ‘product debt’.
The more complexity there is overall in the ‘essential’ domain of the product, the harder changes are going to be make in the future, and as the only thing you guarantee is change and therefore you will have to make changes, you don’t want to model complexity you don’t need to
The product leadership should be made aware of these kinds of debt to factor that into any planning ahead, long or short term, rather than being in the dark due to seeing the issue as purely ‘technical’. You are in a much better situation as a product organisation when you recognise now obsolete or complex product areas that need to be changed from the moment change is required, rather than only finding out after development has begun and having to deliver ‘hacks’ which accrue even more debt than you would have originally.
I always vouch for working with product and design early on for any changes, to think ahead about the model or process that engineering will implement in code, as there are very real consequences not only to engineering velocity but also user experience.
As an aside, there are two usefuls axioms I like to use here when it comes to discussing complexity. The first is the idea that complexity is conserved, never removed or hidden, meaning that if the domain is complex, the total complexity will be reflected in either the user’s experience, or the code that powers the product, or shared across both. The second is that the number of different concepts an average human can hold in their short-term memory is 7, plus or minus 2. I’ve worked on a few products where an internal algorithm or billing system has been completely opaque to everyone except engineering because there are 10 or more variables involved that you can’t minimise further through abstraction. You should think very carefully about addingmore than 7 (plus minus two) variables as it leaves the company at risk if most employees don’t understand how core systems work.
2) Data Debt
This falls in a fuzzy place on the line between product and engineering, and it’s related to a very tough issue to minimise - how do you model the data that powers your product to ensure it is still useful and interpretable in the future, and doesn’t become so impenetrable or obsolete that you stop making data-driven decisions.
This covers everything from designing telemetry and metric systesm, all the way to the core application database structure, and is especially hard to avoid as the consumers of the data aren’t known at the time - will it be queried by data analysts from the data warehouse? Will it be aggregated and passed to a third party through some future revenue making deal?
The reason I pull this out separately from a purely technical concern, and one that should involve product and even your data team if you have one, is that it is much harder to fix badly modelled data than it is to fix bad code, so it’s worth thinking about the potential for future data debt issues when designing data structures. You can’t protect yourself from all unknowns, but you can minimise some of them byt not blindly creating column names or analytics events without thinking about the cost of ambiguity or poor design for future analysis, and use a little empathy to think ahead.
3) Technical Debt
What is left after the above two is closer to what I think ‘technical debt’ should really mean. Namely, given a particular domain or process to model that has been deemed ‘essential’, has it been modelled in a sub-optimal way meaning that means that future changes are harder than they should be? If so, that is technical debt, and is largely the responsibility of the engineering team to tackle, and champion the resource needed to tackle it.
The reasons it exists can be due to a multitide of reasons, including but not limited to issues such as not spending enough time on the design of the code, overcomplicated design, poor test coverage which makes changes harder to make safely, reducing quality vs reducing scope when a deadline loomed leading to poor quality code, amongst many other reasons.g
The key difference to the other two types here is this type of debt is truly the domain of engineering, you dont need to necessarily involve product unless functionality needs to change.
Why bother with these terms instead using ‘technical’ debt for everything? Paying off ‘debt’ in a product’s ecosystem is not purely the job of a tenacious engineer who likes to tidy up bad code, it should be a concern of everyone in the product’s ecosystem. By using these terms my hope would be that you make it clearer to the product function as a whole that the ‘accruing’, ‘leveraging’ and ‘paying off’ of this debt should be part of everyone’s day-to-day thinking, part of roadmaps and planning, and by doing so the inevitable debt built as an unavoidable consequence of building products can be tackled together rather than by engineering alone.
Other useful articles