The productivity claim goes: our engineers now generate 40% of our code with AI, so we ship faster. The unstated assumption is that writing code was the constraint.
It never was. On any codebase old enough to matter, the constraint is comprehension — knowing what the system does, why it does it that way, and what breaks if you change it. Generation collapsed the cost of the cheap half and left the expensive half exactly where it was.
That is the whole story, and everything below follows from it. Technical debt has not changed its nature. It has changed its accrual rate, and most organisations have not changed the rate at which they can absorb it.
The rate mismatch
Here is the uncomfortable arithmetic. Generation is parallel: five engineers with a model produce five streams of code simultaneously, and one engineer can run several at once. Review is serial per person, and reading unfamiliar code is slower than writing familiar code — always was, still is, and no tool has changed it.
So a capability that scales horizontally now feeds a capability that does not. If your review process was already the slow step, generation did not speed you up; it lengthened the queue and made each item in it less familiar.
“We generate 40% of our code” is not a productivity metric. It is a debt-accrual rate, and whether it is good news depends entirely on a number nobody quotes alongside it: whether review depth held.
Four kinds of debt this creates
Generated code produces some debt that hand-written code mostly doesn’t.
Unowned code
The most consequential and the least discussed. Traditionally, code had an author who held a mental model of it. That model was undocumented and lived in one head, which was a risk — but the head existed. You could ask it questions.
Generated code that was reviewed rather than written has no such head. The engineer who approved it understood it well enough to believe it was correct, which is a much weaker state than having designed it. Six months later, when it breaks at 2am, there is no one to interrogate. The commit has a name on it and that name never held the model.
This is not an argument against generation. It is an argument for noticing that “reviewed by” and “authored by” have quietly become different things, while your process still treats them as the same.
Plausible-but-wrong abstractions
Models produce idiomatic code. Idiomatic code passes review, because reviewers are pattern-matching against what good code looks like, and this looks exactly like it.
The failure is when the idiom is right in general and wrong for your domain — a retry that is correct for a read and catastrophic for a payment, a cache that is fine until your consistency requirements are stated, a generic error handler that swallows the one error you needed to see. Hand-written wrong code often looks wrong. Generated wrong code reads beautifully.
Dependency sprawl
Ask for a solution and you tend to get one that imports something. Each import is a permanent obligation: an upgrade path, a CVE surface, a licence, a transitive tree you did not choose.
The marginal cost of adding a dependency at generation time is zero. The marginal cost of carrying it is not, and it is paid by a different team, later.
Duplicated logic
Abstraction exists partly because copying was expensive. When producing the fifth variant of a rule costs nothing, the incentive to unify five variants disappears — and five copies of a business rule will drift, because they are updated by different people at different times.
This is the debt that compounds most quietly. Nothing is wrong on the day it is written. It is wrong eighteen months later, in one of the five places, and nobody knows there are five.
What to measure instead of volume
Lines generated, percentage AI-assisted, and acceptance rate all measure the half that got cheap. Useful indicators measure the half that didn’t:
- Review depth — time spent per changed line, and whether it fell as generation rose. A drop here is the whole problem, visible early.
- Explainability coverage — what share of your critical paths has a named person who can explain the design without reading it first. Sample it; don’t assume it.
- Time-to-first-fix, generated modules versus hand-written ones. If unfamiliarity is accumulating, this diverges before anything else does.
- Dependency delta per quarter — the cheapest early indicator of sprawl.
- Rule duplication — count implementations of your handful of core business rules. Most teams are shocked by this number once.
None of these are hard to collect. They are unpopular because they measure a cost rather than a win, and the win is what got the tool approved.
What actually works
Hold generated code to a stricter bar, not a looser one. This is counterintuitive and it is right. The usual justification for a light review is that you trust the author’s judgement — and here there is no author to trust. The absence of a designer is exactly the reason to raise the bar, not lower it.
Make the submitter explain it. Not review it — explain it, in the review, without reading it again. Why this structure, what happens on the failure path, what you considered and rejected. If they cannot, the code is unowned and you have found that out now rather than at 2am. This single gate does more than any tooling.
Refuse large generated diffs. A 2,000-line pull request was previously self-limiting, because someone had to write it. That limiter is gone and nothing replaced it. Cap diff size and mean it; the cost of splitting is now trivial.
Generate tests separately from code, ideally from the specification rather than from the implementation. Tests generated alongside the code they test encode the same misunderstanding twice and then agree with themselves, which reads as verification and is not.
Delete far more aggressively than you used to. The strongest argument for keeping mediocre code was always that rewriting it was expensive. That argument is substantially weaker now. Sunk cost was doing a lot of work in your retention decisions, and generation removed most of it — a genuine upside, and almost nobody is taking it. If a module is poorly understood and cheap to regenerate, regenerating it from a clear specification is often better than maintaining it.
Where it genuinely reduces debt
Worth being even-handed: there is a category where generated code is strictly better, and it is larger than sceptics allow.
Work nobody was ever going to own — migration scripts, one-off data fixes, throwaway analysis, scaffolding, test fixtures, the internal tool three people use — was always undocumented and unmaintained. Debt requires a future obligation, and this code has none. Generating it is pure gain, and teams that are cautious everywhere are leaving that on the table.
The distinction that matters is not “is this AI-written” but “will anyone need to understand this later?” If no, generate freely and move on. If yes, the ownership question applies regardless of how the code was produced.
The leadership decision
It comes down to one ratio: your organisation’s capacity to produce code against its capacity to understand what it is carrying. Generation moved the first sharply. If you did not move the second, the gap is your debt accrual, and it is invisible for two or three quarters because new code does not fail immediately. It fails when it is changed by someone who was not there.
The concrete version: if you have funded generation tooling and not funded review capacity, documentation, or deletion, you have made a decision without noticing. Budget the second as a fraction of the first, and track the ratio rather than the raw output.
The uncomfortable framing to end on. Most organisations already could not explain large parts of their codebase before any of this arrived. Generation did not create that condition — it removed the last constraint that was limiting it, which was that producing code nobody understood used to require someone to sit down and write it.
Related: How to Evaluate an AI Tool for Your Business on assessing the claims these tools make, and Buy, Build, or Wait on the decision that precedes it.