How to prioritise technical debt like a pro

It can be incredibly frustrating. You want to deliver cool features like the ability for users to talk to an AI chatbot, or a unicorn that joyfully flies across the screen when a user completes a task. But you’re told by your engineers that you have a sizeable amount of technical debt to deal with.

It's best to listen to them. If you've accrued more technical debt than you've serviced, you may see it in the team’s velocity as it starts to drop over time.

“Goodbye unicorn launcher," you say tearfully.

But does it have to be this hard?

Let me break down my preferred approach to dealing with bugs & technical debt and show you how you might be able to keep those pretty unicorns after all.

Get your definitions right

Let’s start with definitions!

  • Bugs: issues that crop up during development or in production, caught by a developer, tester, a user (eek!) or via automated tests.
  • Technical debt: the cost incurred when short-term speed of delivery takes priority over quality, which can be a strategic decision. This results in parts of the codebase having low quality or maintainability, which can later manifest as bugs or as code that is challenging to work with. This can slow down the development team in the long run.

Ward Cunningham, the person who coined the term ‘technical debt’, defined it as follows:

"Shipping first time code is like going into debt. A little debt speeds development so long as it is paid back promptly with a rewrite...The danger occurs when the debt is not repaid. Every minute spent on not-quite-right code counts as interest on that debt. Whole engineering organizations can be brought to a stand-still under the debt load of an unconsolidated implementation, object-oriented or otherwise."

Technical debt is not inherently bad. In fact, it's natural. You just need a methodical way of servicing it.

You may also often hear the term ‘defect’ thrown around. If we’re to be pedantic, one would say a ‘bug’ is a deviation from the intended functionality that impacts the user experience, and a ‘defect’ is a failure in intended functionality…but I prefer to keep it simple. Everything is a bug, just with different severities.

It also makes it easier to label in Jira ;)

Fix bugs during development

Let’s start with bugs found during feature development.

Here we cut to the chase…

…fix the bugs!

It’s not just the developer’s responsibility to ensure quality is being delivered - it’s the team's responsibility. Your Definition of Done should clearly outline that a user story is not done until all bugs that critically impact the user experience have been quashed. The Product lead should encourage this.

However, not all bugs critically impact the user experience, and we have timelines to hit.

Therefore time-pressed developers should aim to fix high severity bugs that impact the user experience. They can document low severity bugs (e.g. cosmetic issues) for fixing later as a form of technical debt.

Bugs-not-fixed are considered technical debt because, by not fixing them today, we make a trade-off to prioritise speed of delivery and accept the possible time, effort, and resources needed to fix these later - the longer these go unfixed, the harder they may become to fix.

The Product lead can help with the prioritisation of these bugs. They would then make sure this is documented in the backlog, which brings me to…

Get everything in the backlog

The Product Backlog is the source of truth for stuff to be done.

So aside from user stories that deliver end-user value, you also want to include technical debt (including bugs intentionally not fixed during development) and prioritise accordingly. You should also include bugs found in production (see the next section).

If you use tools like Jira, you may choose to label bugs as ‘bugs’ and technical debt as either ‘tasks’ or ‘stories’ (the latter would be chosen if there is some end-user value to be gained, however this would be split into tasks during Sprint Planning if you follow a Scrum process).

What epics would these fall under?

The temptation would be to place it in a generic ‘fix technical debt’ epic. Blehhh.

You want to put these into epics which help with later prioritisation. For example, you may put technical debt into ‘fix payments technical debt’ or ‘fix frivolous flourishes technical debt’ (in the case of building the aforementioned unicorn launcher). You can still get specific if it’s not associated with any particular part of the product, such as “update software version” or “improve performance”.

Technical debt won’t always fit neatly into product components, so it’s just about finding categorisation that works for your specific context.

Whatever categorisation you use, you just need to make sure that it helps you to:

  1. identify which part of the codebase the technical debt sits in
  2. keep track of overall technical debt so you can monitor progress vs paying it down

Some may advocate for having a separate technical debt backlog. I feel this is unnecessary if you categorise your technical debt sufficiently enough to find and prioritise it appropriately.

Triage bugs found in production

You will inevitably come across bugs in production, identified by exploratory testing or by users. These should be placed into the backlog as a form of technical debt to make sure the team isn’t distracted from achieving the Sprint Goal, unless a bug:

  1. is of ‘critical severity’, and…
  2. doesn’t risk the Sprint Goal

Every team has their own way of triaging bugs, but here’s an example approach:

  • Critical: an error that prevents / adversely affects the ability for a user to complete an essential task
  • Medium: an error that adversely affects the ability for a user to complete an essential task, for which acceptable alternative workarounds are available and known to them (‘acceptable’ being the key word here)
  • Low: an error that is an inconvenience to the user but has no impact on the ability for a user to complete an essential task (e.g. cosmetic issues)

Note that if you actually are fixing a bug found in production, remember that it’s not closed when the bug is fixed - it’s closed when the team has captured the lessons learned to avoid the same problem in the future.

Prioritise everything

Every good Product Backlog will be appropriately prioritised, continuously adapting to users’ and the business’ needs. This applies to both features and technical debt.

With that in mind, let the prioritisation of your technical debt be influenced by how you prioritise your features - something I learned in ‘Essential Scrum’ by Kenneth S. Rubin.

Here’s an example of a sprint’s worth of work to bring it to life:

an estimated set of feature and technical debt stories

You essentially want to group technical debt with features where the technical debt aligns.

Bear in mind that you should only group together technical debt that you feel is worth servicing when delivering that feature - you wouldn’t want to include something that is too low of a priority to worry about, where the cost of servicing the debt outweighs any benefit to servicing it.

Your technical debt appetite depends on what rough proportion of your sprint you’re happy to allocate to servicing technical debt. In the above example there are 16 total story points, of which 12 points are for features and 4 points are for technical debt. This is a 25% allocation to technical debt. I wouldn’t worry about trying to make it too precise - you can just give yourself a range, e.g. 20 - 30% of the sprint can be for servicing technical debt.

Refine it

Let’s take a look at an updated example which could be part of the wider Product Backlog.

During Backlog Refinement, you might choose to refine ‘Pay with Bitcoin’ by breaking the epic down into smaller user stories and adding acceptance criteria. Once you’ve done this, you can also break down the refactoring task into smaller tasks and add acceptance criteria too.

As you do this, you might find that a few of these smaller stories aren’t as valuable, so you move them down in priority in the backlog. You might also do the same with lower priority tasks for refactoring.

Of course, when you get to Sprint Planning, you can take another call then on whether it makes sense to take on everything as per the backlog priority, or if it makes sense to de-prioritise a few more stories / tasks - especially if you’re finding the technical debt to service is too high vs your tech debt sprint allocation.

Service unavoidable technical debt

So you’ve now got an appropriately refined Product Backlog, with technical debt grouped up nicely with user stories to be delivered.

Great!

You would bring the technical debt into the sprint to be serviced when you bring in the user stories to be delivered.

However, technical debt isn’t always known upfront.

Teams may find they've accrued technical debt via subpar technical practices. They may not be using test-driven development, or carrying out effective code reviews, or they’re not using automated tests, and so on. In an ideal world, you want to avoid this accrual by enforcing strong technical practices and a clear Definition of Done.

In other cases, technical debt is inevitable. Kenneth Rubin calls this ‘unavoidable technical debt’. For example, you’ll find that parts of the codebase need re-work based on new things you learn as business needs evolve. When this debt is discovered, it goes into the Product Backlog.

With that in mind, follow these steps for servicing technical debt:

  1. Develop with strong technical practices

Before we service any kind of technical debt, avoid accruing it where possible. Don’t skimp out on strong technical practices. If you’re going to accrue technical debt, make sure it’s for a strong reason e.g. the benefits of delivering quickly outweigh the costs the technical debt could incur in fixing it later.

  1. Follow the ‘Boy Scout’ rule

When developing a user story, follow the ‘Boy Scout’ rule and leave the codebase better than you found it. The term is derived from the Boy Scouts of America’s motto “Leave No Trace”, essentially encouraging individuals to leave their surroundings better than they found them. A developer should consider:

  • refactoring code (especially if it’s tech debt that wasn’t known before) e.g. simplifying complex code or removing unnecessary code
  • improving documentation e.g. adding comments to code or updating wikis
  • writing automated tests

Of course, it’s up to the developer to make sure they don’t go overboard and end up taking as much (or more) time refactoring as they require for developing the story. If the developer needs a threshold, they can rely on the agreed tech debt allocation (e.g. 20% - 30%) to apply that % to this work. For example, if a feature takes 4 hours to develop, they could spend roughly 1 hour on improving their ‘surroundings’ to be within the allocated range.

  1. Service the agreed technical debt

If you’ve appropriately prioritised your Product Backlog, you will have brought in technical debt to work on which aligns with planned user stories that provide value to the end-user. Service these, and feel at peace knowing you’ve reduced technical debt in a relevant part of the codebase.

In conclusion

It’s easy to get lost with backlog prioritisation when it comes to feature development vs servicing technical debt. You often find yourself feeling like you’re in between a rock and a hard place, with your team telling you that bugs need fixing / technical debt needs servicing, and the business telling you to deliver things quickly.

Hopefully with this how-to, you’re able to expertly blast through bugs and technical debt without sacrificing features considered necessary to achieving business goals.

Happy prioritising!

Want more like this?

Sign up and get notified when an article is published.