Skip to content
Better architecture · 4 min read

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.

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 debtAI debt
OriginA deliberate shortcutAn incorrect inference
Someone knows about itUsually, and often it is in a commentNo
ShapeUgly code that worksClean code in the wrong place
Typical formA hack, marked as oneA duplicate, or a crossed boundary
Found byReading it: it looks wrongNothing, 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.

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 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.

01

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.

02

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.

03

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.

04

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.

checkout-flow · health
GraphFlowsTasksHealthSecurity
2 high2 medium1 info9 finding kinds · deterministic
  • Auth check exists but nothing calls it

    lib/auth.ts → app/api/payments/route.ts

    gate_not_wired
  • Refund handler returns success without refunding

    app/api/refunds/route.ts

    stub_shortcut
  • Two handlers answer POST /api/checkout

    app/api/checkout/route.ts

    duplicate_endpoint
  • Nothing imports this module

    lib/legacy-cart.ts

    orphan_file
  • Receipts is drawn but connects to nothing

    graph node · service:receipts

    isolated_node

Hypotheses until an agent pass confirms them. A scanner that overstates its confidence gets ignored.

Structural findings read off the graph: duplication, orphans, crossed boundaries.

Common questions

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.

Draw it. Then keep it true.

Graphlit turns an architecture drawing into a typed graph, builds against it, and proves the code still matches. Free to start.

Keep reading