Technical Debt

Technical Debt Explained: How Audits Quantify It

“Everything takes longer than it used to”

That sentence, usually said by a founder, usually with a sigh, is how most technical debt conversations start. Features that took a week now take a month. Estimates are wrong in one direction only. Your best engineer spends half her time explaining why “just add a checkbox” touches eleven files.

Everyone agrees there’s debt. Nobody can say how much, where it is, or which parts are actually costing money versus merely offending someone’s aesthetics. That last distinction matters enormously, because “refactor everything” is not a plan, and “ignore it all” is a slow-motion incident.

This is the specific problem a code audit solves: turning technical debt from a feeling into an inventory.

What technical debt actually is (and isn’t)

Ward Cunningham’s original metaphor was precise: debt is shipping code you know is imperfect to learn faster, then repaying it with what you learned. The interest is the extra effort every future change costs until you do.

The metaphor gets abused in two directions:

  • “All old code is debt.” No. Old, stable, rarely-touched code that works is an asset. Ugly code nobody modifies charges no interest.
  • “Debt means someone screwed up.” Sometimes. But plenty of debt was a rational trade at the time: the startup that skipped multi-tenancy to close its first customer made the right call. The failure isn’t taking the loan; it’s forgetting you have one.

It also helps to distinguish debt from plain defects. A SQL injection hole isn’t technical debt, it’s a vulnerability; that category gets its own treatment in top security vulnerabilities code audits catch. Debt is structural: the code works today, but its shape taxes every tomorrow.

The useful taxonomy for audits has four buckets:

  1. Deliberate and documented: “we hardcoded the currency, ticket #142 tracks it.” Cheap to manage.
  2. Deliberate and forgotten: the same shortcut, but the ticket never existed and the person left. This is most of what we find.
  3. Accidental: the team didn’t know a better pattern. Common in codebases built by a single junior developer or, increasingly, generated by AI tools.
  4. Environmental: the code stood still while the ecosystem moved. Deprecated APIs, EOL runtimes, abandoned libraries.

How an audit puts numbers on it

Automated tools will happily print you a “technical debt ratio” and an estimate like “247 days to fix.” Treat these the way you’d treat a horoscope: occasionally resonant, not decision-grade. Static analysis can count rule violations; it cannot know that half of them sit in a reporting module you’re deleting next quarter. That gap between counting and judging is the core of the manual vs. automated audit question.

A manual audit quantifies debt along three axes instead:

1. Interest rate: what does this cost per change?

The key measurement is churn × complexity. Take your version control history:

# Files changed most often in the last year
git log --since="1 year ago" --name-only --pretty=format: \
  | sort | uniq -c | sort -rg | head -20

Cross-reference the top of that list with complexity metrics (cyclomatic complexity, file length, coupling). Files that are both high-churn and high-complexity are where your team bleeds hours every sprint. A 2,000-line God-class that’s in the top ten of that churn list is expensive debt. The same class, untouched for two years, is nearly free.

In our experience, this analysis usually shocks teams: 70 to 80% of the “interest payments” concentrate in fewer than ten files.

2. Principal: what would repayment cost?

For each hotspot, an auditor estimates the remediation honestly: extract this module (days), add characterization tests then refactor (a sprint), replace this dependency (weeks, because it leaks into everything). Ranges, not false precision. The point is relative sizing so items can be ordered, which is the subject of how to prioritize fixes after an audit.

3. Risk: what happens if you don’t pay?

Some debt is a productivity tax. Some is a loaded gun:

  • An EOL runtime that stops receiving security patches
  • A single database with no tested restore procedure
  • Auth logic copy-pasted across services, each copy drifting separately
  • The one deployment script only one ex-employee understood

These get flagged separately from “this code is hard to change,” because their cost model isn’t linear; it’s a small probability of a very bad day.

What the numbers look like in practice

A useful audit output is not a score. It’s a table you can act on. A condensed real-world shape (details changed):

ItemTypeInterestPrincipalRisk
OrderService (1,800 lines, in 40% of PRs)AccidentalHigh (touched weekly)~2 sprintsMedium
Node 16 runtime (EOL)EnvironmentalLowDaysHigh
No staging environmentDeliberate, forgottenMedium~1 weekHigh
Legacy PDF moduleAccidentalNone (untouched 18 mo)~3 weeksLow

The last row is the important one. A naive tool would flag the PDF module hardest; it’s probably the ugliest code in the repo. The audit says: leave it alone, it’s charging you nothing. Spend that budget on OrderService and the EOL runtime instead.

Why quantifying debt changes the conversation

Unquantified debt produces two dysfunctional states. Either engineering can’t get refactoring time because “it works, ship features,” or engineering gets a vague mandate to “clean things up” and burns a quarter polishing code that didn’t matter.

A costed inventory fixes both. The CFO conversation stops being “trust me, the code is bad” and becomes “these four items cost us roughly a sprint per quarter in drag, and items two and three each carry outage risk; here’s the repayment plan interleaved with the roadmap.” That’s a conversation about capital allocation, which is a conversation businesses know how to have. The downstream costs of not having it (slower delivery, failed diligence, the eventual rewrite) are covered in the real cost of unaudited code.

The other shift is psychological. Named, sized, and scheduled debt stops being ambient dread. Teams that can point at the list argue less and fix more.

Get a free code audit

Building this inventory yourself is possible (the churn analysis above is one command), but interpreting it takes someone who has read a lot of other people’s codebases. That’s the service. We offer a genuinely free manual code audit: a Webisoft engineer reviews your repository, maps the debt hotspots against your actual change history, flags the loaded-gun risks, and delivers a written, prioritized report. Free means free: no invoice appears at the end, and you keep the report either way. Get a free code audit and find out what your debt actually costs.

FAQ

Frequently asked questions

Is all old or ugly code technical debt?

No. Old, stable code that nobody modifies is an asset, however ugly it looks, because debt only charges interest when you have to change the code around it. The debt worth worrying about sits in files that are both complex and frequently touched, which is why an audit weighs complexity against change history instead of judging code in isolation.

How is technical debt actually measured in an audit?

Along three axes: the interest rate (what each change costs, found by cross-referencing high-churn files from git history with complexity metrics), the principal (an honest range for what repayment would take), and the risk (what happens if you never pay). The output is a ranked table you can act on, not a single score.

Should I trust the technical debt ratio my static analysis tool reports?

Treat it as a rough signal at best. Tools can count rule violations, but they cannot know that half of them sit in a module you plan to delete, or that the ugliest file in the repo has not been touched in eighteen months. Deciding which debt actually costs money requires judgment about your change history and roadmap, which is the manual part of an audit.

What is the difference between technical debt and a plain bug or vulnerability?

A vulnerability like a SQL injection hole is a defect: the code does something wrong today. Debt is structural: the code works today, but its shape makes every future change slower or riskier. Audits treat them separately because the fixes, the urgency, and the cost models are different.

Which technical debt should we pay down first?

Start with items that are both high-interest and high-risk: code that is complex, changed constantly, and sitting near something dangerous like an end-of-life runtime or untested restore procedure. Interest payments tend to concentrate in a small number of files, so a modest, well-aimed repayment budget usually buys back most of the drag. Debt that charges nothing can be consciously left alone.

Free · no strings shown later

Get your free code audit

A repo URL and two minutes of your time. A Webisoft engineer sends back written findings (security, tech debt, performance) with file-and-line specifics.

Request my free audit