Code Review Is Broken at Most Companies — Here Is What Fixes It
Table of Contents
- The Inverse Relationship Nobody Talks About
- Why Large Pull Requests Get Approved
- Review Latency Compounds
- What Reviewers Should Actually Look For
- Automate Everything Subjective
- Writing a Reviewable Change
- Comment Patterns That Reduce Friction
- Metrics Worth Tracking
- Common Pitfalls
- Conclusion
- Frequently Asked Questions
Key takeaway: Review quality is inversely proportional to change size, and most teams’ review problems are actually change-size problems wearing a process costume.
The Inverse Relationship Nobody Talks About
There is a consistent pattern in how engineers review code, and it is counterintuitive until you notice it in yourself.
A 20-line change receives detailed scrutiny. Reviewers question variable names, suggest alternative approaches, spot the missing null check, ask about the edge case.
A 2,000-line change receives “LGTM.”
This is not laziness. It is a predictable response to cognitive load. Reviewing code requires holding the change and its context in working memory simultaneously — what this function did before, what calls it, what invariants existed, what the change alters. Past a few hundred lines, that becomes impossible, and the reviewer shifts from evaluating correctness to assessing plausibility.
Plausibility assessment produces approval. The code looks like code that would work. The tests are present. The author is competent. Approve.
The consequence is uncomfortable: the changes most likely to contain serious problems — large, complex, wide-reaching ones — receive the least effective review. Your process is inverted relative to risk.
Everything else in this article follows from this single observation.
Why Large Pull Requests Get Approved
Understanding the mechanism matters because the obvious fix — “review more carefully” — does not work. Reviewers are not choosing to review poorly.
Working memory has hard limits. Holding a dozen interacting changes in mind while evaluating each is beyond most people’s capacity. This is not a discipline problem.
Social cost of blocking rises with size. Requesting changes on 20 lines costs the author minutes. Requesting a restructure of 2,000 lines costs days of work already completed. Reviewers feel that asymmetry and calibrate accordingly, often unconsciously. Sunk cost pressure operates on reviewers, not just authors.
Reviewer fatigue is real and cumulative. Attention degrades measurably through a long review. Problems in later files receive less scrutiny than problems in earlier ones — which is why the last file in a large diff is a reliable place for bugs to survive.
Large changes mix concerns. A pull request containing a refactor, a feature, a dependency bump, and formatting changes forces the reviewer to context-switch continuously. Each switch loses accumulated understanding.
The structural implication is that review effectiveness is not primarily a function of reviewer skill or diligence. It is a function of what you ask reviewers to do. Asking someone to meaningfully review 2,000 lines is asking for something humans do not do well.
Review Latency Compounds
The second failure mode is throughput, and it is worse than it appears because delays multiply rather than add.
A pull request waiting eight hours for review does not cost eight hours. It costs eight hours plus the author’s context-switch away and back, plus any conflicts accumulated in the interim, plus the review cycle if changes are requested, plus a second wait. A change that could merge in an hour routinely takes two days.
Worse, latency changes author behaviour in damaging ways. Engineers who know review is slow batch their work into larger pull requests to reduce the number of review rounds. Larger pull requests are harder to review, which increases latency, which encourages further batching. This feedback loop is how teams end up with a culture of thousand-line changes nobody genuinely reads.
Breaking the loop requires attacking latency directly:
Treat review as interrupt-driven work, not background work. A team norm of reviewing within a few hours, with explicit review time blocked in the day, changes throughput dramatically.
Cap work in progress. If reviewing is optional and shipping is measured, review loses. Making review queue depth visible and treating it as shared responsibility corrects the incentive.
Use draft pull requests for early feedback. Directional feedback at 10 percent complete is cheap. The same feedback at 100 percent complete is expensive and frequently gets suppressed.
Escalate stale reviews automatically. A bot pinging pull requests idle beyond a threshold removes the social awkwardness of chasing colleagues.
What Reviewers Should Actually Look For
Much review time is spent on things that either do not matter or should be automated. A useful hierarchy, most valuable first:
Correctness of intent. Does this change accomplish what it claims? This requires understanding the goal, which requires the author to have stated it. A pull request without a description of why cannot be reviewed at this level.
Failure modes. What happens when the network call fails, the input is empty, two requests arrive simultaneously, the value is null, the list is enormous? Authors reason about the success path; reviewers add most value reasoning about the rest.
Interface and contract design. Function signatures, API shapes, and data models are expensive to change later because callers accumulate. A poor internal implementation is a local problem; a poor interface is a lasting one. This deserves disproportionate attention.
Security and data handling. Injection surfaces, authorisation checks, secrets in code, personal data in logs. Automation catches some of this; reasoning about authorisation logic remains human work.
Test adequacy. Not test presence — adequacy. Do the tests exercise the failure modes, or only confirm the happy path the author already verified manually?
Readability for the next person. Will an engineer unfamiliar with this code understand it in six months? Naming and structure matter here, though this is where reviewers most often over-invest relative to value.
Explicitly not worth human attention: formatting, import ordering, line length, trailing whitespace, and most stylistic preference. Every minute spent on these is a minute not spent on failure modes.
Automate Everything Subjective
The single highest-leverage change most teams can make is removing style discussion from human review entirely.
Style debates are unwinnable because they are preference disputes framed as technical arguments. They consume review time, generate friction between colleagues, and produce no defect reduction. The resolution is to remove the decision from the interpersonal domain: adopt a formatter, run it automatically, and stop discussing it.
# Everything below this line should never appear in a human review comment
- formatting # formatter, run pre-commit and in CI
- import ordering # linter with autofix
- unused variables # linter
- type errors # type checker
- known vulnerabilities # dependency scanner
- test coverage floor # CI gate
- commit message format # commit linter
Two principles make this work. The formatter’s output is not up for debate — if the team dislikes a rule, change the configuration once and reformat the codebase, rather than arguing per pull request. And automated checks must run before review, not after, so reviewers never see a change that would fail them.
Teams that do this consistently report that review comments shift almost entirely toward logic, design, and edge cases. That shift is the entire point.
Writing a Reviewable Change
Authors control most of review quality, which is underappreciated. A well-constructed pull request gets better review than a poorly constructed one from the same reviewer.
Keep it under 400 lines. Empirical work on defect detection consistently finds review effectiveness dropping sharply beyond a few hundred lines. Under 400 is a reasonable target; under 200 is better.
One concern per pull request. Refactoring, feature work, dependency updates, and formatting should be separate. If you must refactor to implement a feature, do the refactor as its own change first — it is reviewable in isolation, and a pure refactor with passing tests is fast to approve.
Write the description for someone with no context. What problem this solves, what approach you chose, what alternatives you rejected, what you are uncertain about. Explicit uncertainty is valuable: “I am unsure whether this lock is necessary” directs reviewer attention precisely where it helps.
Annotate your own diff first. Reading your own change as a reviewer would catches an embarrassing number of issues and lets you pre-empt questions with inline comments explaining non-obvious decisions.
Make commits tell a story. A reviewer who can read commit-by-commit has a much easier task than one facing a single squashed diff. This is worth the effort on larger changes specifically.
Comment Patterns That Reduce Friction
How feedback is phrased affects whether it gets acted on. A few conventions help substantially.
Label the severity explicitly. Prefixing comments removes ambiguity about whether something blocks:
blocking:must be addressed before mergesuggestion:improvement, author decidesnit:trivial preference, feel free to ignorequestion:genuine request for understandingpraise:this is good, worth noting
Without labels, authors treat every comment as blocking, which slows everything and breeds resentment over trivia.
Ask rather than assert when uncertain. “What happens if this list is empty?” invites reasoning. “This will break on empty lists” invites defensiveness and is sometimes wrong.
Explain the reasoning, not just the change. “Use a set here” is an instruction. “Use a set here — this is O(n²) with a list and the input can be large” teaches something transferable.
Note what is good. Reviews consisting exclusively of criticism are demoralising and provide no signal about what to repeat. This costs nothing and materially affects whether people enjoy submitting work for review.
Metrics Worth Tracking
A small number of measures reveal most review dysfunction:
| Metric | Healthy range | What a bad number indicates |
|---|---|---|
| Median PR size | Under 200 lines | Batching due to slow review |
| Time to first review | Under 4 working hours | Review not prioritised |
| Time to merge | Under 1 day | Latency or too many required approvers |
| Review rounds per PR | 1–2 | Unclear requirements or late feedback |
| Percent approved with no comments | Under 30% | Rubber-stamping |
That last row is the one most teams never measure and should. A high proportion of comment-free approvals on non-trivial changes is direct evidence that review is ceremonial rather than functional.
Use these to find process problems, never to evaluate individuals. Metrics applied to people get optimised — reviewers leave token comments to avoid appearing to rubber-stamp, and authors split changes artificially. Both make things worse while improving the numbers.
Common Pitfalls
Requiring too many approvers. Two reviewers on every change doubles latency and produces diffusion of responsibility. One engaged reviewer beats three cursory ones.
Reviewing for style you could automate. Every such comment displaces a comment about behaviour.
No description on the pull request. Reviewers cannot evaluate whether code achieves its purpose without knowing the purpose.
Blocking on preferences. Reserve blocking for correctness, security, and interface design. Preference belongs in suggestions.
Review as gatekeeping. If review functions as a status ritual where senior engineers demonstrate authority, people will route around it. Review is collaborative error-finding.
Letting review be the only quality mechanism. Types, tests, static analysis, and observability all catch classes of problem more reliably than human reading. Review should focus on what only humans can evaluate.
Conclusion
Most code review problems are size problems. Large changes cannot be reviewed effectively regardless of reviewer skill, and slow review causes engineers to create large changes. That loop is the root cause behind most complaints about review culture.
Break it from both directions. Reduce change size, aggressively, by separating refactors from features and shipping smaller increments. Reduce review latency, deliberately, by treating review as urgent work rather than something done when convenient.
Then remove everything mechanical from human attention. Formatters, linters, type checkers, and scanners handle style, obvious errors, and known vulnerabilities better than reviewers do — and they never generate interpersonal friction doing it. What remains for humans is intent, failure modes, interface design, and whether the next engineer will understand this code. That is where review earns its cost.
Frequently Asked Questions
What is a good maximum pull request size? Under 400 lines of substantive change, excluding generated files and lockfiles. Under 200 is better. Beyond that, review effectiveness drops sharply regardless of who is reviewing.
Should every change require review? For anything touching production behaviour, yes. Documentation typos and generated file updates are reasonable exceptions. The value comes from consistency — case-by-case exemptions become gradual erosion.
How many reviewers should be required? One engaged reviewer for most changes. Two for security-sensitive code or areas where knowledge is concentrated in too few people. More than two increases latency without proportional benefit.
Is pair programming a substitute for review? It substitutes for the correctness-checking function, since two people saw the code as it was written. It does not provide the fresh-eyes perspective a reviewer with no context brings, which is where readability problems surface.
How do I raise review quality without seeming critical of colleagues? Attack structure rather than behaviour. Propose a size limit, add automation, set a latency expectation. These change outcomes without implying anyone is reviewing badly — which they usually are not, given what they are being asked to do.
What if the author disagrees with feedback? Discuss synchronously rather than through comment threads, which escalate poorly. If it remains unresolved, the code owner decides for implementation details and the team decides for interface changes. Unresolved disagreement blocking a merge for days is worse than either outcome.
Should reviewers run the code? For anything with meaningful behavioural change, checking out and running it catches problems reading cannot. Most reviewers skip this, which is why bugs that only manifest at runtime survive review.