Your linter passes, your types check out, and the tests go green. Then three days after merge, a user hits a state the author never tested and the bug is yours now.

Logic bugs are the ones that survive review because they don't look wrong. The code does something. It just doesn't do the right thing under every condition the system will eventually produce.

This post is a practical breakdown of how to catch them before they ship.

Why Logic Bugs Survive Code Review

Linters catch style. Type checkers catch mismatched shapes. Neither catches a function that silently returns the wrong value when a list is empty. That part is on the reviewer.

The problem is that most reviewers read code for what it does, not what it's supposed to do under every possible condition. Under time pressure, the review narrows to surface concerns: naming, formatting, obvious nulls. The deeper question, whether the logic holds at the edges, gets skipped because nothing looks broken.

Fatigue compounds this. By the fifth file in a large PR, attention degrades. The bug that ships is usually the one nobody thought to question, often buried in a helper function that looks routine.

Read the PR Description Before a Single Line of Code

The description tells you the intended behaviour. The diff tells you the actual behaviour. Mismatches between the two are exactly where logic bugs hide.

If there's no description, that's your first signal to pause. Before reviewing a single line, ask what problem this PR is solving. Without that anchor, you're reading code in a vacuum and pattern-matching for style rather than verifying intent.

Once you have the description, pick a specific user action or system state it references. A feature that "handles pagination" means something concrete: a user clicks next, the offset increments, the query returns the right slice. Hold that scenario in your head while you read the diff. When the code diverges from that scenario, you've found something worth questioning.

Trace the Happy Path, Then Break It

Walk through the code once assuming everything works. Follow the data from input to output along the route the author expected. This gives you the mental model you'll need for the next step.

Then start breaking it. Ask what happens when the input is empty, zero, negative, null, or a value the author didn't anticipate. Not all of these will be realistic, but the discipline of asking stops you from unconsciously filling in assumptions.

Boundary conditions deserve specific attention: loops (does it run zero times correctly?), array accesses (what happens on the last index?), and pagination offsets (what does page one look like vs. page zero?). These are where off-by-one errors and silent data loss tend to live.

Also watch for early returns that skip validation and conditional branches that assume a state the caller doesn't guarantee. A branch that runs only when a flag is true is fine, as long as the flag is always set before that branch is reachable. Often it isn't.

Spot the Assumptions the Author Made Without Knowing It

Most logic bugs aren't careless mistakes. They're invisible assumptions. That a list has at least one item. That two async operations resolve in the order they were called. That a flag resets between requests.

The author wrote code that works in their mental model of the system. Your job as a reviewer is to find the places where that model and the actual system diverge.

Look specifically for code that works in development but depends on data the production environment won't always provide: a database that always returns rows in a test fixture, an API that's always fast in local testing, a session that's always populated in a logged-in dev environment.

Race conditions and stale state fall into this category too. They're not implementation bugs in the sense that the logic is written wrong. They're assumption bugs: the code assumes a guarantee the system doesn't make.

The question to ask is: what has to be true for this code to work? Then ask whether the system actually guarantees it. If the answer is "usually" or "in the happy path," you've found a logic bug waiting to surface.

Build the Habit With Deliberate Practice

Reading about review technique builds awareness. It doesn't build the pattern-recognition that makes catching logic bugs fast and automatic. That part comes from repetition with feedback.

The difference between a reviewer who catches edge cases reliably and one who misses them isn't knowledge of the theory. It's reps. The experienced reviewer has seen a version of this bug before and knows which questions to ask.

Goodcatch is built around this: graded reviews on real pull requests, with scoring on what you caught and what you missed. The stack-specific tracks cover React, Vue, Svelte, Angular, Laravel, Django, Rails, Node, Spring Boot, ASP.NET Core, Next.js, and more, so the practice stays relevant to the code you actually write day to day.

The free trial runs in-browser with no account required, which means you can check the difficulty level before committing to anything. Each track has eight reviews across multiple difficulty tiers.

Honest note: if you already do senior-level reviews at high volume, the junior and mid tiers will feel easy. Start at the tier that actually challenges you. The practice is only useful if it's putting pressure on the skills you haven't fully built yet.

Track Where You Keep Missing Things

A single missed bug is a bad day. Missing the same category of bug across multiple reviews is a skill gap, and it won't close by itself.

Weak-spot analytics, available on paid plans, surface which bug types you consistently overlook. That's a different kind of useful than a score. A score tells you how you did. Weak-spot data tells you where to focus next.

Use it to pick specific tracks or difficulty tiers rather than cycling through reviews at random. If you keep missing async ordering issues, do more reviews in tracks where that pattern appears. If you're consistently catching security holes but missing logic errors in conditional branches, that's where your next block of practice goes.

The progression from junior to lead isn't just motivational framing. The grading criteria shift at each tier to reflect what reviewers are actually expected to catch at that level. A junior reviewer who spots a null-pointer issue gets credit. A lead reviewer is expected to catch the race condition that the null check was masking. The bar moves, which means the practice keeps being useful as you improve.

Pricing starts free with three graded reviews. Unlimited access, all difficulty tiers, and weak-spot analytics are available at $19/month, $45 for a three-month sprint, or $144/year. Full details are on the pricing page.


Start a free graded review on Goodcatch, no account needed.