Technical Debt Inventory: How to Build One

Every team knows they have technical debt. Almost nobody can tell you exactly what it is, where it lives, or how much it costs. Building an inventory is the first step towards making debt visible and actionable.

Human process: a meeting table with a few chairs, a whiteboard with blank boxes, and a single notebook open to a page with simple shapes (no words)

Ask any engineering team whether they have technical debt and the answer is always yes. Ask them to list it, and you get vague gestures. “The authentication module is a mess.” “The reporting service needs a rewrite.” “There is something wrong with how we handle caching.”

These are symptoms, not a diagnosis. And symptoms are not actionable. You cannot prioritise a feeling. You cannot estimate the cost of “a mess.” You cannot make a business case for fixing “something wrong.”

What you need is an inventory: a structured, categorised list of specific issues, each with a location, a severity, and enough context to estimate the cost of leaving it and the effort of fixing it. This is how you build one.


Why most teams do not have an inventory

The reason is not laziness. It is that building a technical debt inventory manually is extraordinarily time-consuming. It requires reading code that nobody has looked at in years. It requires understanding systems that have evolved through multiple teams, frameworks, and architectural philosophies. It requires a level of holistic analysis that is fundamentally different from the daily work of shipping features.

PR review catches change-level issues. Static analysis catches rule violations. But neither produces a comprehensive view of the debt that exists across the entire codebase. The debt is distributed. It lives in the gap between what the code does and what the team believes it does. It lives in the inconsistencies between modules that were written by different people at different times with different assumptions.

Building an inventory requires someone – or something – to read the whole thing and assess it systematically.


Step 1: Define your categories

Not all technical debt is the same. Before you start cataloguing issues, you need a taxonomy. Without categories, your inventory becomes an undifferentiated list that is difficult to analyse and impossible to prioritise.

A practical categorisation includes: security (vulnerabilities, exposure risks, missing validation), performance (slow queries, unnecessary computation, missing caching), architecture (circular dependencies, inconsistent patterns, tight coupling), error handling (swallowed exceptions, inconsistent error types, missing fallbacks), testing (untested critical paths, brittle tests, missing integration tests), dead code (unused functions, unreachable branches, deprecated modules), and documentation (misleading comments, undocumented public APIs, stale configuration files).

You can refine these categories to match your specific context. The important thing is that every finding belongs to exactly one category. This lets you filter, sort, and report on debt by type, which is essential for prioritisation.


Step 2: Gather findings systematically

There are three sources of findings for a debt inventory, and you should use all three.

Automated analysis. Start with what tools can tell you. Run your static analyser across the full codebase. Collect linter warnings that have been suppressed or ignored. Run dependency audits. Check for known vulnerabilities in third-party packages. These automated findings are the low-hanging fruit. They are also the least interesting part of the inventory, because they only capture what rules can express.

Team knowledge. Your developers know where the bodies are buried. Conduct structured interviews or surveys. Ask each team member to identify the three parts of the codebase they dread working in, and why. Ask them what they work around rather than through. Ask them what they would fix if they had two uninterrupted weeks. This surfaces the tacit knowledge that exists in people's heads but has never been written down.

Systematic code review. This is the most valuable and most difficult source. It requires reading the codebase holistically – not just the files that changed recently, but the modules that have not been touched in years. It requires examining how components interact, whether patterns are consistent, whether error handling is coherent across boundaries. AI-assisted code review tools like VibeRails can perform this analysis across an entire codebase, producing categorised findings with locations, severity assessments, and descriptions.


Step 3: Assess severity

Every finding needs a severity level. Without severity, your inventory is just a list. With severity, it becomes a prioritisation tool.

A four-level severity scale works well for most teams. Critical: active security vulnerability or data integrity risk. Must be addressed immediately. High: significant performance issue, reliability risk, or architectural problem that affects development velocity. Should be addressed within the current quarter. Medium: code quality issue that increases maintenance burden or onboarding time. Should be scheduled. Low: minor inconsistency or improvement opportunity. Address opportunistically.

The key principle is that severity should reflect impact, not effort. A one-line fix to a critical security issue is still critical severity. A two-week refactoring of a low-risk module is still low severity. Separating severity from effort is essential because it prevents teams from prioritising easy fixes over important ones.


Step 4: Record location and context

A finding without a location is not actionable. Each item in your inventory should include the specific file and function (or line range) where the issue exists, a clear description of what the issue is, why it matters (the impact), and what a fix would look like (even if the fix is complex).

Context is what separates a useful inventory from a complaint list. “Error handling is inconsistent” is a complaint. “The payment processing module in src/payments/processor.ts catches all exceptions and returns a generic 500 error, losing the original error context. This makes debugging payment failures require log correlation across three services and typically takes 45 minutes per incident” is an actionable finding.

The more specific each finding, the easier it is for someone to pick it up and act on it without needing to rediscover the problem from scratch.


Step 5: Estimate cost and effort

For each finding above low severity, estimate two things: the ongoing cost of not fixing it, and the effort required to fix it.

Ongoing cost can be measured in hours per quarter. How much time does the team spend working around this issue? How many incidents does it cause? How much does it slow down onboarding? These numbers do not need to be precise. A rough estimate is far more useful than no estimate at all.

Effort should be estimated in developer-days or t-shirt sizes (S, M, L, XL). Be honest about uncertainty. A finding that requires refactoring a core module has higher uncertainty than one that requires adding input validation to a single endpoint.

With cost and effort estimates, you can calculate a simple return on investment for each finding: how much time does fixing this save per quarter, relative to the effort of fixing it? This gives you a prioritised list ordered by value, not just severity.


Step 6: Maintain the inventory

A debt inventory is not a one-time artefact. It is a living document. New debt is created with every sprint. Existing debt is paid down (or not) over time. The inventory must be updated regularly or it becomes stale and loses its value as a decision-making tool.

The practical approach is to update the inventory quarterly. Re-run automated analysis. Review resolved and unresolved findings. Add new findings from code reviews and team feedback. Reassess severity for existing items, because the impact of debt changes as the system evolves.

Some teams integrate their debt inventory with their existing project management tools, creating tickets for high-severity findings and linking them to the inventory for context. Others maintain a standalone document or spreadsheet. The format matters less than the habit. What matters is that someone owns the inventory and that it is reviewed at a cadence that matches your planning cycles.


What changes when you have an inventory

The moment you have a categorised, severity-assessed technical debt inventory, the conversation about code quality changes fundamentally. You are no longer arguing about whether debt exists or whether it matters. You are looking at a list of specific issues with specific costs and specific fixes.

Sprint planning becomes more informed. When someone proposes a feature that touches a debt-heavy module, the team can see the associated risks and factor in time for remediation. Roadmap discussions become more grounded, because you can quantify the cost of deferring maintenance. Stakeholder conversations become more productive, because you can express technical debt in terms of hours, incidents, and money rather than abstractions.

An inventory does not fix your debt. But it makes the debt visible, and visibility is the precondition for every fix that follows.


Limits and tradeoffs

  • It can miss context. Treat findings as prompts for investigation, not verdicts.
  • False positives happen. Plan a quick triage pass before you schedule work.
  • Privacy depends on your model setup. If you use a cloud model, relevant code is sent to that provider; local models can keep inference on your own hardware.