A bug is hard to spot in a pull request when the surrounding code is locally coherent: each line reads as correct on its own, and the flaw only appears when you hold the whole function's contract in mind at once. The second miss usually comes from a fixed search pattern, where your brain marks an area as checked and stops routing attention there on re-review.
If you've ever approved a PR, watched a bug reach production, then re-read the diff and thought "how did I not see that", you're in familiar territory. The problem isn't attention span or experience level. It's that code review has structural properties that hide certain defect classes from even careful readers. Understanding those properties is the first step to working against them.
Why some bugs hide in plain sight
Reviewers read with the author's intent in their heads, not the machine's execution path. That means they confirm what they expect rather than test what's actually there. A function called validateInput that only sanitises, not validates, passes a casual read because the name sounds right and the surrounding code is tidy.
Size compounds the problem. A 600-line PR gives a reviewer too much surface to hold in working memory. The brain starts skimming well before the end, and the defect that would have jumped out in file three stays invisible in file fifteen.
Naming is one of the most reliable covers. When a name almost fits, the reader's pattern-matching fills in the gap. The code looks purposeful. The intent seems clear. The gap between what the function is called and what it actually does only shows up when you read the contract, not the label.
What makes the same bug easy to miss a second time
The second miss follows a different mechanism. Once you've scanned an area and found nothing, your brain marks it as cleared. Re-reviewing that area takes conscious effort to override, and most reviewers don't override it because they have no signal that something was missed the first time.
Copy-paste chains spread a single flaw across several files. After a reviewer clears the first instance, they unconsciously extend that clearance to the copies. The assumption is that if the original was fine, the copy is fine. That assumption breaks every time the context differs even slightly.
Familiarity with an author's style builds a reading shortcut. Reviewers who know a colleague's habits start predicting rather than reading. That's fast, but it's lossy. The defects that slip through are precisely the ones that don't fit the predicted pattern, which means they're the least expected and often the most significant.
Review fatigue compounds all of this. A defect in the fifteenth file of a PR is far less likely to be caught than the same defect in the third.
Which categories of defect are most reliably missed
Logic errors that satisfy the happy path
Logic errors that only surface on edge inputs are the most consistently missed category. Reviewers mentally run the happy path through the code, not adversarial cases. An off-by-one in a loop index that passes every test case in the PR description but silently truncates the last record in production is a textbook example: the code looks complete, the tests pass, and the flaw only appears at a boundary no reviewer thought to probe.
These errors are also the hardest to catch with static tooling, because they're syntactically valid. The compiler has no complaint. The linter has no complaint. Only a reviewer who asks "what happens when the input is empty, or when it contains exactly one item" will find it.
Security flaws dressed as convenience
Security holes that look like sensible shortcuts are the second most missed category. The code appears to be solving a real problem in a direct way, so the reviewer's instinct is to approve the pragmatism rather than interrogate the risk.
An unparameterised query written for a one-off admin endpoint, a JWT verified without checking the algorithm field, a file path built from user input without normalisation: each reads as practical until you ask what an attacker would do with it. When you're reviewing a PR that introduces a new external dependency or integration, the surface for this class of defect widens significantly. How to review a pull request that introduces a new external API integration covers the specific patterns to check in that context.
Tracking which of these categories you personally miss most is the starting point for targeted improvement.
How do you build the habit of catching what you'd normally skip
Read the diff as an adversary, not a collaborator
The fastest way to change what you catch is to change the question you're asking. Instead of "does this work", ask "how would this break, and who benefits from breaking it". That reframe shifts your attention from confirming intent to probing assumptions.
Concretely, that means tracing every external input through the function regardless of what the author says it is, and checking every early return for what it leaves in an inconsistent state. A code review checklist built around bug classes, not line-by-line style gives you a repeatable structure for that adversarial pass.
Practise on graded pull requests
Deliberate practice on real PRs with scored feedback closes the loop that normal code review leaves open. In ordinary work, you almost never find out what you missed. The bug either surfaces later with no clear trail back to the review, or it stays dormant.
Goodcatch offers stack-specific tracks across React, Vue, Django, Rails, Laravel, and others, where each reviewed PR is graded on what you caught and what you didn't. Your weak spots become visible rather than staying hidden. A free trial runs in-browser with no account required. Paid plans start at $19 a month for unlimited graded reviews and weak-spot analytics.
Working through the difficulty tiers in order builds pattern recognition faster than reviewing at the same difficulty repeatedly, because each tier introduces a new class of disguise. The tiers that feel uncomfortable are the ones doing the most work.
When does better tooling help, and when does it not
Static analysis and linters catch a well-defined class of bugs reliably and cheaply. They genuinely remove that class from the reviewer's responsibility, and that's worth having. A reviewer who doesn't have to check for unused imports or obvious null dereferences has more attention for the things a linter can't reach.
What tools don't catch: logic errors that are syntactically valid, security flaws that depend on business context, and any defect that only appears across the interaction of two modules. Logging and observability code is a good example of this last category: the individual lines look fine, but the interaction with the broader system is where problems hide. How to review pull requests that touch logging and observability code explains why that area deserves its own pass.
Automated tools are a floor. A codebase with a linter still needs reviewers who can reason about what the code is supposed to do, not just what it does.
If your goal is to improve your own review skill, adding more tooling to your pipeline doesn't train the skill. It substitutes for it. Goodcatch is the wrong choice if what you need is automated coverage at scale. It's the right choice if you want to sharpen the judgment that tools can't replace.
How do you track whether your reviews are actually improving
Improvement is measurable when you have a consistent signal: the same type of PR, reviewed under the same conditions, graded against the same standard over time. Without that consistency, you're estimating, and most developers overestimate their own review quality because the feedback loop in normal work is too slow and too noisy.
Weak-spot analytics, like those in Goodcatch's unlimited plan, surface the categories you miss repeatedly. That lets you focus practice time on the gaps that cost you the most, rather than drilling areas where you're already competent.
A junior developer working through a structured track from easy to lead-level difficulty gets a visible progression that random PR review on the job doesn't provide. The skills that compound over time are different from the ones that plateau. Code review skills that compound over a career versus skills that plateau maps that distinction in detail.
Progress stalls when practice stays comfortable. The tiers that feel too hard are the ones worth spending time on.
Frequently asked questions
How is a code review different from just reading code?
A code review has a specific goal: find defects, verify the change does what it claims, and check that the approach is sound before the code merges. Reading code for understanding is a different task with a different focus, and the mental mode you use for one doesn't transfer automatically to the other.
Does practising on artificial examples actually help with real PRs?
It depends on how realistic the examples are. Practising on sanitised toy code builds familiarity with syntax rather than with the structural patterns that hide real bugs. The value of practising on real pull requests, graded against what an expert reviewer would catch, is that the disguise patterns match what you'll see on the job.
What's the fastest way to get better at spotting security issues in a PR?
Build a short mental checklist of the input paths: where does user-controlled data enter, where does it get used, and what's between those two points. Security flaws in PR reviews almost always involve user input that travels further than the reviewer assumed, or trust that's extended without verification. Adversarial questioning, asking what an attacker would do rather than what the author intended, is the single fastest shift you can make.