Code Review When Code Is Cheap
Review practice under high change volume. What a reviewer should look for when the author did not type the code, why pull request size discipline matters more than before, and who owns a defect nobody wrote by hand.
Code review was always doing several jobs at once, and most teams never separated them. It caught defects. It spread knowledge. It enforced consistency. It provided an audit trail. It socialised newcomers into how the team thinks. It gave a second person enough context to support the change at three in the morning. These jobs coexisted comfortably because one activity — a colleague reading your diff — served all of them adequately.
That bundle has come apart. When the author did not type the code, the knowledge-spreading job is weaker in one direction and more important in the other: the reviewer may now be the first human to have thought carefully about this code at all. When change volume rises, the consistency job scales badly and the defect-catching job becomes the constraint on the entire delivery system. And when nobody typed it, the accountability job — which was always implicit — needs to be made explicit, because the implicit answer has stopped working.
This article is about review practice under those conditions. It assumes you accept the argument that verification is now the bottleneck; if you do not, the recommendations here will look like unnecessary ceremony.
The reviewer is no longer the second person to think about this
Start with the change that matters most, because everything else follows from it.
The old review model had an implicit safety property that nobody wrote down. Before a reviewer saw a change, a competent engineer had already made every decision in it deliberately. They had chosen the data structure, considered the error path, decided what to do when the input is empty. The review was a second opinion on decisions that had definitely been made by someone who understood the consequences.
That property no longer holds. A generated change contains decisions that may not have been made by anyone — defaults, error handling, retry behaviour, ordering assumptions, concurrency assumptions. They are present in the code and they are plausible, which is precisely the problem. Plausible code that nobody decided on reads exactly like code somebody decided on carefully, because the surface markers of care are the easiest thing to reproduce.
So the reviewer's job has shifted from second opinion to first opinion, at least for the decisions the author did not consciously make. That is a materially harder job, it takes longer per change, and it arrives at a higher rate.
What to look for, in priority order
Reviewer attention is the scarcest resource in the system. Spend it where the machine cannot go and where the cost of being wrong is highest.
Does this solve the right problem? The most expensive defects are not incorrect code but correct code doing the wrong thing. Generation makes this failure mode more common, because a slightly misread intent produces a complete, working, well-structured implementation of the wrong requirement, and nothing about its appearance signals the mismatch.
Are the invariants preserved? Every non-trivial system has things that must always be true: this balance never goes negative, this identifier is unique per tenant, this operation is idempotent, this collection is never mutated after publication. These are rarely written down and rarely enforced by types. They are the things a generated change is most likely to violate, because they are context the generator did not have and the author may not have thought to supply.
Is the error and edge behaviour deliberate? Not present — deliberate. Empty input, partial failure, timeout, duplicate delivery, out-of-order arrival, concurrent modification. Generated code usually handles these in some way. The question is whether the way it handles them is the way your system needs, which is a domain question, not a code question.
Does it fit the design we already have? Duplicated abstractions, a second HTTP client, a parallel retry policy, a new way of representing an existing concept. This is the erosion of what Brooks called conceptual integrity, and it is the most insidious cost of high change volume: each change is individually reasonable and the aggregate is incoherent. Only a human with the whole system in mind catches this, and it is precisely the thing a saturated reviewer skips.
What is the blast radius? Is this change independently releasable and independently reversible? Does it touch shared state, a migration, a public contract? Review depth should scale with consequence, and consequence is not proportional to diff size.
Is there anything here nobody can explain? A dependency that appeared for no stated reason. A clever construct doing something subtle. A configuration value with no derivation. If the author cannot explain why a line is there, it should not be there — this is the cheapest and most effective single question in modern review.
Notice what is not on that list. Formatting, naming conventions, import ordering, obvious null dereferences, common injection patterns. All of that should be caught automatically before a human opens the change. Every item you move from human attention to automated checking is a direct increase in the capacity of your constraint.
Size discipline matters more, not less
There is a seductive argument that if code is cheap to produce, large changes are fine. It is exactly backwards, and the error comes from thinking about the production cost rather than the review cost.
Review effort does not scale linearly with diff size. It scales worse, because the reviewer must hold interactions in their head, and interactions grow combinatorially. A four-hundred-line change is more than four times the work of a hundred-line change, and beyond some threshold — different for every reviewer and every codebase — it stops being a review at all and becomes an approval.
The threshold has not moved. What has moved is how easily you cross it. It used to take a week of typing to produce a two-thousand-line change, and that week gave everyone time to notice it was getting out of hand. Now it takes an afternoon, and the review queue is the only place the cost shows up.
| Change size | What review actually is | What gets caught |
|---|---|---|
| Under ~100 lines | A careful read | Logic, invariants, design fit, edge cases |
| ~100 to ~400 lines | A read with skimmed sections | Most logic, some design fit |
| Over ~400 lines | Pattern-matching and trust | Style, obvious errors, little else |
| Over ~1000 lines | An approval | Approximately nothing |
Treat those bands as shape rather than measurement; the exact numbers depend on your codebase and your people, and anyone who quotes a precise universal threshold is making it up. The shape is what matters: there is a size above which review stops functioning, and cheap generation makes it trivially easy to exceed.
This is why trunk-based development and small-batch integration became more valuable rather than less. The same batch size economics that govern releases govern pull requests, and the transaction cost that matters for a pull request is the reviewer's time, which did not fall.
Who owns the defect
This is the question that gets asked nervously and deserves a blunt answer.
The author owns it. The person who submitted the change owns every line of it, regardless of how it was produced. Submitting a change is an assertion that you have read it, understood it, and believe it is correct. If you cannot make that assertion about a change you are submitting, you should not be submitting it.
This is not a new principle. It is the same principle that has always applied to code copied from an internal library, adapted from a public example, or inherited from a departing colleague. Provenance has never transferred accountability. What has changed is the volume of code to which the principle applies and the ease of not noticing that you have stopped applying it.
Three consequences follow, and they are worth stating explicitly in your engineering standards rather than leaving to inference.
"The assistant wrote it" is not a defence in a post-incident review. It is a statement about tooling, not about causation. The useful version of the question is why the review process did not catch it, and that is a process finding rather than an individual one.
Understanding is a submission criterion. If you cannot explain a line in your change, remove it or understand it. This is enforceable in review with one question and it does more for quality than most testing initiatives.
The reviewer shares accountability, as always. Approving a change is a professional judgement. Review is not a formality and an approval is not a receipt. If reviewers are approving changes they have not genuinely read, that is a capacity problem to fix rather than an integrity problem to punish, but it should be named accurately.
Practices that hold up under volume
Automate everything mechanical, ruthlessly. Formatting, lint, type checking, dependency policy, common security patterns, coverage floors. This is capacity creation for the constraint.
Use assistants as a pre-reviewer, never as the reviewer. A machine pass that flags candidate issues before a human reads the change is genuinely useful; it catches a class of routine problems and costs nothing. It does not form a judgement about intent, it does not know your invariants, and it cannot be accountable. Treat its output as a checklist to consider, not as a review that happened.
Require the author to state intent in the description. What should be true after this change that was not true before, and how do you know. This single requirement improves review quality more than any tooling change, because it forces the author to do the thinking that generation let them skip.
Review tests before implementation. If the tests are right and genuinely derived from the specification rather than the implementation, much of the implementation review becomes cheaper. If the tests are wrong, nothing downstream matters. Read them first.
Rotate reviewers deliberately. Knowledge concentration is a capacity risk on your constraint. Pairing and rotation are not just development practices; they are how you widen the bottleneck.
Keep a running list of invariants. A short document per service stating the things that must always be true. It costs an hour to write, it is the highest-value context you can give both a reviewer and an assistant, and almost nobody has one.
What to do on Monday
Pull the size distribution of your merged changes over the last quarter. If the median has risen year on year, you have measured the problem and you can stop debating whether it is real. Set a size threshold, publish it, and give reviewers explicit permission to bounce changes that exceed it.
Add one field to your pull request template: what should be true after this change that was not true before. Nothing else. Watch what happens to review comment quality over a month.
Then take the service your team most recently broke and spend an hour writing down its invariants. Put the list in the repository. It will improve human review immediately, and it is the context that makes generated changes markedly better as a side effect.