Technical Debt: How to Recognize and Address It

September 15, 2023

Technical debt is identifiable before it becomes a crisis. The signals show up earlier than most teams acknowledge them.

The first one is the estimate problem. When developers start adding significant buffer to tasks that should be straightforward — adding a field to a form, changing a validation rule, updating a report — and they can't clearly explain why, the buffer is usually debt. The code paths involved have accumulated enough complexity that every change requires archaeology before it requires engineering. Nobody announces this. It just shows up in estimates that seem too large for what's being asked.

The second signal is avoidance. Parts of the codebase that developers route around rather than through. The file nobody wants to open. The module that gets blamed when bugs appear even when the connection is indirect. When a team has collective anxiety about a specific area of the code, that area is almost certainly where the most consequential debt lives.

How to Measure It

Measuring technical debt is useful because it reframes the conversation from "the code is messy" to "here's what this is costing us." The most practical measurement is time-based: how long does it take to make changes in the problematic areas versus how long similar changes take in cleaner parts of the codebase? The ratio gives you an interest rate. If changes in the legacy module take three times as long as equivalent changes elsewhere, the debt is compounding at 200% above what clean code would cost.

The risk-based measurement matters more for prioritization: what's the business impact if the high-debt areas fail? A billing integration that nobody understands and everyone avoids carries different risk than a legacy admin panel that three people use.

Paying It Down Without Stopping Everything

The approach that works in practice is incremental, not a dedicated cleanup sprint. Dedicated cleanup sprints almost always get deprioritized or cut short. The Boy Scout rule — leave code better than you found it — is more reliable because it attaches debt reduction to work that's already happening. If you're changing a module anyway, you clean what you touch. Over time, the highest-traffic parts of the codebase improve because they get touched most often.

The parts of the codebase that never get touched are where the risk concentrates. Those are the ones that warrant deliberate attention: not a full rewrite, but enough time to understand what they do and add characterization tests that document current behavior. You don't have to make the code better to reduce the risk. You just have to understand it well enough that a change doesn't become a surprise.

Budget for it explicitly. If debt reduction doesn't have a line in the sprint, it doesn't happen. Twenty percent of sprint capacity is a reasonable starting point for codebases with meaningful accumulated debt. Less than that and it doesn't move. More than that and it starts competing visibly with feature delivery in ways that create organizational pressure to cut it.


Working through a problem like this?

Describe the system and where it's stuck. I'll tell you what the work actually involves.

Get in touch