This code review checklist groups what to look for by bug class rather than by file order. It covers logic, security, state, error handling, performance, and maintainability. The single item most reviewers miss is security input handling: it requires thinking like an attacker, and most people read code top to bottom without switching that mode on.
If you've ever approved a PR that looked fine and then watched it fail silently in production, this checklist is for that situation. Work through each section before you submit your review, not after you've already commented on naming and formatting.
Logic and Control Flow
Logic bugs are the most expensive class to catch late because they rarely throw an exception and they often ship without a trace. A bad boolean operator or a misplaced guard clause will pass every test that wasn't written to catch it.
- Check every branch is reachable. Trace the conditions and confirm no branch is dead code or always-true. Dead branches create confusion and sometimes hide logic that was meant to run.
- Look for off-by-one errors in loops and ranges. These appear most often in pagination, slice operations, and retry limits. Ask whether the boundary is inclusive or exclusive and whether the code reflects that.
- Verify early-return and guard-clause order. A misplaced guard can allow execution to continue when it should have stopped. Read the guards top to bottom as a sequence, not just individually.
- Confirm boolean expressions use the intended operator. A
&&where||was meant, or vice versa, is easy to write and easy to miss. Read the full expression out loud if you're unsure. - Trace async control flow separately. Check that awaited calls are not skipped, and that parallel calls that must be sequential are not fired with
Promise.all. Async bugs are invisible until they cause a data ordering problem under load.
Spotting logic bugs before they reach production takes a specific approach that's worth reading alongside this section.
Security and Input Handling
Security issues are the bug class reviewers skip most often because catching them requires switching mental models. You can't find them by reading what the code does; you have to ask what it allows.
- Check every point where user input enters the system. Database queries, file paths, shell commands, and HTML output each need their own mitigation: parameterised queries, path sanitisation, and proper escaping respectively.
- Look for hardcoded secrets. API keys, passwords, and tokens in source files are a common early-career mistake and a serious production risk. Check new files and config changes especially.
- Verify authentication checks come before business logic. A route that fetches data before confirming the caller has access exposes that data to any unauthenticated request.
- Confirm rate limiting or input size caps on public endpoints. Unbounded inputs are a denial-of-service risk even in internal tools, and they're easy to add at the route level.
- Check dependency versions for known CVEs if the PR bumps a package. Tools like
npm auditorpip-audittake under a minute. Cite the output in your review comment so the author can see what you ran.
Security issues junior developers most often miss in pull requests covers several of these in detail with concrete examples.
State and Data Integrity
State bugs are disproportionately hard to reproduce in testing, so catching them in review saves hours of debugging under real conditions.
- Check for mutation of shared state without a lock or copy. This applies to in-memory caches, module-level variables, and React state objects passed by reference.
- Look for race conditions between concurrent writes. Ask whether two requests arriving at the same millisecond would both read a stale value and write conflicting results.
- Verify database transactions wrap operations that must succeed or fail together. A PR that inserts a record and then updates a counter in separate queries can leave data inconsistent if the second query fails.
- Confirm nullable fields are handled before use. A missing null-check on a field that is null in any realistic scenario will crash in production, and it's fast to verify.
- Check that caches are invalidated when the underlying data changes. Stale reads can be as damaging as corrupt writes, and cache invalidation logic is easy to forget when a new write path is added.
Error Handling and Observability
A PR that adds no error handling will fail silently, and silent failures are the hardest class of incident to diagnose. Look for swallowed exceptions and empty catch blocks before anything else in this section.
- Check that every thrown error is either caught and handled or deliberately allowed to propagate. Swallowed exceptions hide bugs that would otherwise be obvious in logs.
- Look for catch blocks that log nothing. Catching an error and doing nothing removes the only signal you'd have had during an incident.
- Verify that error messages give enough context to act on.
Error: failedtells an on-call engineer nothing.Error: payment charge failed for order 8821, Stripe returned card_declineddoes. - Confirm that structured logging is used where the codebase already uses it. Free-text logs are hard to query in tools like Datadog or CloudWatch, and inconsistency makes them harder still.
- Check that failing paths return appropriate HTTP status codes or error types to callers. A 200 with an error body is a contract violation that breaks clients in ways that are difficult to trace.
Performance and Resource Management
Performance issues rarely block a PR from passing review, which is exactly why reviewers should look for them deliberately rather than waiting for a production slowdown.
- Check for N+1 query patterns. A loop that runs a database query per iteration is one of the most common and most avoidable performance bugs, especially in ORM-heavy stacks like Rails or Django.
- Look for unbounded queries on large tables. A
SELECTwithout aLIMIT, or a query that scans a full table on every request, will scale poorly as data grows. - Verify that connections, file handles, and streams are closed after use. Unreleased resources cause memory leaks and connection pool exhaustion under load.
- Check that expensive computations are not repeated inside a loop when the result is stable. Move them above the loop and note it in the comment so the author understands the reasoning.
- Confirm that background jobs or queues are used for work that does not need to block the HTTP response. Synchronous email sending inside a request handler is a common mistake that adds latency for every user.
Reviewability and Maintainability
A working PR that nobody can maintain safely is a liability. Correctness is necessary but not sufficient, and this is the section most style-focused checklists collapse into a single vague item.
- Check that the PR does one thing. A PR that refactors, adds a feature, and fixes a bug should be split. Mixed intent makes every other check harder and makes the git history useless.
- Look for functions longer than the screen. Long functions aren't always wrong, but ask whether the complexity is necessary and whether a helper would clarify intent.
- Verify that names describe what a variable or function does, not how it does it.
getUserPaymentStatusis reviewable.doThingis not. - Confirm that tests cover the new behaviour and at least one failure path. A PR with no tests for an error path leaves that path unverified in every future refactor.
- Check that comments explain why, not what. A comment that restates the code adds noise. A comment that explains a non-obvious constraint or a known limitation adds real value.
If you're earlier in your review career, the habits that keep junior developers stuck at junior is a direct read on what separates reviewers who progress from those who don't.
Frequently asked questions
What are the top code review tools?
The most widely used tools are GitHub Pull Requests, GitLab Merge Requests, and Bitbucket Pull Requests, which are built into their respective platforms. Dedicated tools include Gerrit (common in large organisations and open-source projects), Phabricator (used by Meta internally before open-sourcing it), and Review Board. For automated static analysis layered on top, Reviewdog, SonarQube, and CodeClimate are common choices. The right tool depends on where your code already lives.
Can ChatGPT do a code review?
ChatGPT can flag obvious syntax issues and point out common anti-patterns. It has no awareness of your auth model, your threat surface, or what your organisation considers acceptable risk. Use it as a first-pass filter, not a final review.
How do I pass a code review interview?
Reviewers at hiring stages look for whether you catch bug classes first and raise style only after. Working through a checklist like this one aloud shows structured thinking, which is what the interviewer is grading. Name the class of bug you're looking for before you describe the specific line.
Sources
- npm audit documentation checked
- pip-audit project checked