Tech debt with intentionality

Posted on Jun 16, 2024

There’s loads of discussions around tech debt, whether it’s a good analogy in the first place, how to avoid it, and how to strike the right balance between making something work versus making something right.

There’s also some ramblings about how it’s inevitable, how people up the chain and stake-holders are sometimes not aware enough about the actual issues that it inflicts on the developers down in the trenches, and/or do not give it enough consideration.

Finally, we can find great practical advice about how to handle tech debt, with one of my favorites being this article from the Riot Games tech blog: a taxonomy of tech debt.

However there’s an aspect which I’d like to dive into a bit more. It sometimes feels like tech debt is considered mainly as something that’s there, as something that simply appears given enough time. Or that’s created on the spot out of poor design decisions or coding.

But it’s not always the case, as sometimes, having or creating tech debt is a choice, so today I’d like to focus on that, and specifically: intentionality.

Debt? Investment? Loan?

It doesn’t really matter what you want to call it. I see tech debt as the natural consequences of writing imperfect software in an environment which is constantly evolving. But I also see tech debt as a useful tool that can help achieve certain objectives.

It is the natural consequence of a shifting environment, with constraints, requirements, external factors, — basically the world — moving on and evolving. Code written at a certain time will deal with whatever existed and/or was anticipated at that time. It’s inevitable that in the future, the conceptualizations and the world model on which this code was based will gradually drift away from the reality. In my experience, the focus is generally on this aspect of tech debt, that always end up propping up around the code base in various locations and in diverse forms.

But tech debt can also be a tool, because it can be handy to cut corners sometimes to get to an implementation which requirement is that it works, quickly, and not that it handles absolutely every case imaginable1.

A conscious choice

It’s always hard to commit to cutting corners. As software engineers we sometimes feel strongly about code quality, designing and implementing something as cleanly as possible. However, sometimes there are imperatives that are incompatible with that to some degree. Whether it’s a lack of time, resources, a crucial need to validate a concept before investing into it, there are situations which force a pragmatic approach to cut on code quality.

But this doesn’t have to make us completely oblivious to the fact that we’re focusing on other metrics, and thus doing things a little bit messier. We don’t have to ignore the fact that in a way we are creating (contracting?) tech debt. I think we should be deliberate and intentional when we do that. When we decide to ignore some edge cases, skip some level of abstraction, or have coarser error-handling, for example.

Instead we can rather consciously choose to accept that we will end up with some form of tech debt, potentially in the short term even, as long as it makes sense2.

May I have your intention please

By being intentional we can achieve multiple things:

  • Serenity

By virtue of being a stated and conscious choice, it can relieve some of the frustration and/or anxiety that sometimes props up from making a quick and dirty implementation, by simply saying “ok, we’re fine now going with code that only handles X, and won’t have Y”. This can be particularly valuable for some people, my younger self included, to have that sort of stamp of approval that doing things that way is actually valuable and desirable in this specific case (not in general, obviously).

  • Documentation

It’s a choice, and we can document it! Listing every corners cut, every shortcut taken, every edge case left aside. This can be very simple. In a former role, I’ve started things off by creating and maintaining a simple “Future improvements” document3 which listed everything that wasn’t great about our software, as soon as we encountered it or decided to cut a corner. By regularly reviewing it, it was easy to check whether the choice we’d made were still acceptable as the environment evolved. It was also a goldmine of information for when we wanted to act on this tech debt and actually fix it.

We had a clear description of e.g. why we’d only handle fonts this way, why we went with this dependency instead of that other, supposedly better one, how we might need a better and more robust way of generating IDs for this or that data, how our renderer-agnostic code could be even more flexible, whatever. This was also a place to record our initial ideas how to do things properly for when we’d get to work on these topics.

This would save a tremendous amount of time down the road. We could jump back in pretty quickly because all the context was there, instead of having to rebuild it, sometimes nearly from scratch because it was a long time ago. The what, the why, and even as I mentioned some ideas and options for the how.

We increased our velocity both when “contracting” tech debt, and when “repaying” it, in some way. It also mapped a lot of it in a single place, giving a good overview of the areas where the software could be lacking, and helping us quickly identify which components were the most at risk of becoming an issue when tackling new work.

  • Accountability

I’m not sure this one can be systematic, but with intentionality comes some sort of openness, since we make it clear that we will compromise in a way. It needs the buy-in of people involved (at least in an open and empathetic4 culture). It clearly states what tech debt has been contracted and why, and gives the opportunity to challenge the decision. Like a lot of things in software development, it’s a tool and it needs to be considered whether it’s the right one for the job at hand.

Acknowledging that may help realizing when that shouldn’t be used, when cutting corners is the wrong approach, and where it is rather desirable to maximize design and code quality metrics to achieve certain goals.

In the end

There will be tech debt, whether it’s by choice, or as a consequence of the world around our code evolving.

While controlling the latter is harder, I think we can make the intention about the former more explicit and save a lot of time, while avoiding some pain and frustrations as a result.


  1. taking the two extremes here, but the idea remains. ↩︎

  2. as for any tool, “shiny hammer, everything may now look like a nail, you know the drill”. ↩︎

  3. as an aside, I found it nicer to call that “future improvements” rather than say, “current debt” or “current issues”, as having a positive name implied (rightfully so) that it was bound to improve in the future, and that it wasn’t going to be left aside. Names matter, and for that too. ↩︎

  4. trust me, I will come back to the topic of empathy in software. ↩︎