Technical Debt: How to Measure It and How to Argue for Fixing It


Technical debt is the most overused and least precisely used term in software engineering. It’s applied to everything from a global variable someone regrets, to a decade-old authentication system that predates HTTPS, to code that looks messy but works perfectly. When everything is “technical debt,” the term loses meaning and the prioritization conversations become impossible.

The original metaphor, from Ward Cunningham, is worth remembering: technical debt is like financial debt. Taking on debt can be a rational choice that lets you move faster now, with the understanding that you’ll pay interest until the principal is repaid. The debt isn’t inherently bad. Not knowing you have it, or choosing never to repay it, is where it becomes a problem.

Types of Debt Worth Distinguishing

Martin Fowler’s technical debt quadrant is more useful than the generic term. It separates debt by two dimensions: deliberate vs inadvertent, and reckless vs prudent.

Deliberate/prudent: “We know there’s a better design, but we’re shipping now and will refactor once we learn more.” This is rational. The team made a conscious tradeoff.

Deliberate/reckless: “We don’t have time to do this right.” This is accumulated negligence. It often doesn’t feel like a choice in the moment.

Inadvertent/prudent: “Now we understand how we should have done this.” Code that made sense when written but is now understood to be suboptimal. Learning produces this debt; it’s a sign of a growing team.

Inadvertent/reckless: “What’s a layered architecture?” Code written without enough knowledge to do it well.

The type matters for how you approach it. Deliberate/prudent debt needs a plan to repay. Inadvertent debt needs investment in the team’s skills as much as the codebase.

Measuring Debt

You can’t manage what you can’t measure, but most teams have no systematic way to track technical debt. The result: debt accumulates invisibly until it causes an incident or a feature takes five times longer than expected.

Useful proxies for debt burden:

Velocity trend over time: if feature development is getting slower as the codebase grows, debt is accumulating faster than it’s being repaid. A healthy codebase should maintain roughly constant velocity per engineer.

Bug recurrence rate: bugs that keep reappearing in the same area often indicate structural problems, not surface issues. Fixing the symptom without addressing the underlying structure is borrowing at compound interest.

Time to onboard a new engineer: how long does it take a new team member to ship their first meaningful change? A heavily indebted codebase is hard to navigate and understand. Onboarding time is a real cost that’s often not attributed to debt.

Test coverage and confidence: a codebase that engineers are afraid to change without carefully testing everything manually is indebted. Fear of the codebase is a leading indicator of future incidents.

Deployment frequency: if deploying is risky enough that the team avoids it, technical debt is costing you lead time and increasing incident risk.

What Debt Is Worth Paying

Not all debt needs to be repaid. Some code is in areas that never change; debt there is dormant and costs nothing. Some debt is in systems that are being replaced; paying it down is wasted investment.

Debt worth prioritizing:

In the path of current work: if you’re changing an area frequently, debt there multiplies. Every feature in that area costs extra because of the debt. Cleaning it up pays dividends on every future change.

Around system boundaries: messy interfaces between components propagate complexity everywhere that interface is used. A clean interface to a messy implementation is recoverable. A messy interface that many things depend on is much harder to fix later.

In the critical path for reliability: technical debt in authentication, payment processing, or data integrity code is higher risk than in reporting code. Prioritize by blast radius.

That’s causing incidents: if the same area produces repeated incidents, the debt is actively expensive. The cost is measurable in incident hours and customer impact.

Debt that’s safe to ignore:

  • In code that hasn’t changed in years and isn’t planned to change
  • In areas with good test coverage that would catch any regression
  • In code that’s being actively replaced by a different system

Making the Case for Cleanup

Engineers fail to get time for technical debt work for two reasons: they don’t ask, or they ask in terms that don’t resonate with decision-makers.

“The code is messy and hard to work with” is not a compelling case for a product manager. It doesn’t answer “what’s the cost if we don’t do this” or “what’s the benefit if we do.”

Effective framing connects debt to business outcomes:

Velocity argument: “The authentication module has accumulated significant complexity. Features in that area are taking 2x as long to ship as equivalent features elsewhere. A two-week cleanup investment would recover that overhead within a quarter.”

Risk argument: “This part of the codebase has produced three production incidents in the last six months. Each incident cost on average 4 hours of engineering time and impacted N% of users. Addressing the underlying structural issue would reduce incident frequency in this area.”

Opportunity cost: “We’d like to implement feature X. To do it safely, we need to address this debt first. Ignoring it means we can’t ship X, or we ship X with high risk.”

The pattern: express the debt in terms of specific costs (time, incidents, customer impact) and specific benefits of paying it. Make the ROI calculation explicit, even approximately.

The Boy Scout Rule as a Default

Waiting for a dedicated “debt cleanup sprint” often doesn’t work. Business priorities compete, the sprint gets cancelled, and debt continues to accumulate.

The Boy Scout Rule (leave the campsite cleaner than you found it) works better as a default: whenever you work in an area, make it slightly better. Rename a confusing variable, extract a method, add a test for something that’s currently untested, fix a small inconsistency.

This approach distributes the cost of cleanup across regular feature work, requires no special justification, and prevents debt from concentrating. Over time it keeps the codebase livable.

Large structural changes still need dedicated time - you can’t extract a module incrementally while shipping features in parallel. But the Boy Scout Rule prevents the slow accumulation that makes large cleanups necessary more often.



Read more