# Technical debt from AI coding: how to measure it

> AI does not produce more technical debt so much as a different kind, one the usual measurements were not designed to see.

**Source:** https://graphlit.co/blog/ai-technical-debt

**Published:** 2026-08-09

Technical debt was defined as a deliberate trade: take a shortcut now, pay interest later, and repay when you can. The definition assumes somebody *chose*.

AI-generated debt breaks that assumption. Nobody chose. It accumulates as a side effect of changes that each looked correct, which is why the standard measurements keep reporting that everything is fine.

## How it differs

|  | Human debt | AI debt |
| --- | --- | --- |
| Origin | A deliberate shortcut | An incorrect inference |
| Someone knows about it | Usually, and often it is in a comment | No |
| Shape | Ugly code that works | Clean code in the wrong place |
| Typical form | A hack, marked as one | A duplicate, or a crossed boundary |
| Found by | Reading it: it looks wrong | Nothing, until it causes an incident |

That third row is the one that defeats existing tooling. Traditional quality tools look for code that *looks* bad: long functions, deep nesting, high complexity. Generated code scores well on all of it. It is well-formatted, consistently named, and reasonably decomposed. It is also, sometimes, the second implementation of something that already existed.

> **Clean code is not the same as coherent code**
>
> A codebase can pass every linter, hold high test coverage, keep complexity low, and still have three ways to validate an email, two paths into the database and a service nothing calls. Every file is fine. The system is not. Quality metrics measure files.

## Four signals that actually work

These are structural rather than stylistic, which is why they catch what the usual metrics miss.

### 1 · Duplicate responsibility

Count the places that do the same job: validate the same thing, format the same value, call the same external service. Not copy-pasted text, which clone detectors already find, but *semantic* duplication: two different implementations of one responsibility. This is the single most reliable indicator of generated debt.

### 2 · Boundary crossings

Count how many distinct components reach a given resource. If eleven places query the database directly, you no longer have a data layer. You have eleven, and any schema change is now a repository-wide search.

### 3 · Orphan rate

The proportion of code nothing reaches. Orphans mean changes routed *around* existing code rather than modifying it, which is the characteristic signature of an agent that could not find the right place and made a new one.

### 4 · Map-to-code divergence

If you have a stated architecture, the count of things in the code with no counterpart in it, and vice versa. This is [architecture drift](https://graphlit.co/blog/architecture-drift) expressed as a number, and it is the most direct measure available.

## Metrics that mislead here

- **Test coverage.** Generated code often comes with generated tests, which can be high-coverage and assert almost nothing meaningful. Coverage measures execution, not verification.
- **Cyclomatic complexity.** Models write short, shallow functions. Low complexity with the logic spread across four duplicated paths is worse than one honest function.
- **Lines of code.** Volume is no longer a proxy for anything. It costs nothing to produce and says nothing about coherence.
- **Static analysis warnings.** Genuinely useful for catching real bug classes, and blind to the structural problems above by design.

## Keeping it visible

Debt you can see is manageable. The specific danger of the AI variety is invisibility. Nobody made a choice, so nobody has it on a list.

1. **Measure structure on every change, not quarterly**: The four signals above are cheap to compute and only useful as a trend. A number that appears in a review once a quarter is a report; a number that moves on every pull request is a control.
2. **Treat a new boundary crossing as a review comment**: Not a build failure necessarily, but a change that adds the twelfth direct database caller should say so out loud, because nobody reading the diff will notice on their own.
3. **Give duplication a home**: When the check reports two implementations of one responsibility, the resolution is a decision: merge them, or state why both exist. Either is fine. Silence is not.
4. **Keep the intended architecture written down**: You cannot measure divergence without something to diverge from. This is the prerequisite for the fourth signal and most of the value of the other three.

Graphlit computes these structurally: the graph knows which nodes own which files, so duplicate responsibility, orphaned code, unbuilt nodes and boundary violations are queries against a map rather than an afternoon of reading.

*[Interactive demo: Structural findings read off the graph: duplication, orphans, crossed boundaries.]*

> **These are hypotheses, not verdicts**
>
> A structural signal says "these two things look like the same responsibility". It does not know your domain, and sometimes two similar-looking validators are correctly separate. The value is in surfacing the question early enough to answer cheaply, not in being right without you.

## Frequently asked

### Does AI-generated code create more technical debt?

Not necessarily more, but a different kind. Human debt is usually a known shortcut; AI debt is an unnoticed inference: a duplicate implementation or a crossed boundary. It is harder to manage mainly because nobody knows it is there.

### How do you measure technical debt in AI-generated code?

Structurally rather than stylistically: duplicate responsibilities, how many components reach a shared resource directly, the proportion of unreachable code, and divergence between the stated architecture and the actual one. Traditional quality metrics score generated code well while missing all four.

### Is test coverage a good measure of AI code quality?

Weakly. Generated tests can achieve high coverage while asserting very little, because coverage measures which lines execute, not whether anything is verified. A better check is whether the suite fails when you deliberately break the logic.

