Code Review Checklist
Per-PR review workflow a reviewer runs before approving a pull request. Covers context gathering, style, tests, design, security, and merge sign-off — with branches for oversized PRs, database migrations, and dependency changes.
PR Intake and Context
-
Read the PR description and linked ticket
Open the linked Jira/Linear ticket and confirm the PR scope matches what was agreed during refinement. PRs that drift from the ticket — added refactors, opportunistic cleanups, scope creep — are the most common source of review friction. Flag scope mismatches in a top-level review comment before going line-by-line.
-
Check out the branch locally
Don't review only in the GitHub/GitLab diff view for non-trivial PRs. Pull the branch, run it, exercise the changed code path. The diff hides cross-file context; running it locally surfaces issues like broken local dev setup, missing env vars, or unrunnable tests.
-
Confirm CI is green on the latest commit
Don't review a red build. If a check is failing, ask the author to fix or explicitly flag it as a known flake before continuing. "Just rerun it" becomes a habit that masks real regressions.
-
Categorize the PR size
Review quality drops sharply on PRs over ~400 lines of net diff. Categorize before you start reading; if it's oversized, request a split rather than skim-approving with "LGTM".
Collects list -
Ask the author to split the PR
Suggest a split along natural seams: refactor before behavior change, infra/config before app code, schema migration before consuming code. Pause review until the split lands; reviewing a 1,500-line PR reliably means rubber-stamping it.
Code Style and Readability
-
Run the linter and formatter on the diff
If your toolchain has ESLint, Prettier, RuboCop, gofmt, ruff, or equivalent — these should already run in CI. Spot-check that the configured rules actually ran on the changed files (no skipped files via .eslintignore or //eslint-disable). Style nits are for the linter, not the reviewer.
-
Review naming and dead code
Flag ambiguous variable names (data, tmp, handler2), unused imports, commented-out code blocks, and stray console.log / puts / print debug statements. These are cheap to fix now and expensive to clean up later.
-
Confirm complex logic has inline comments
Comments should explain why, not what — the code shows the what. If a regex, recursive call, or non-obvious algorithm has no comment, ask for one. Bonus: a TODO/FIXME with a ticket link is acceptable; a TODO with no owner is not.
Tests and Coverage
-
Verify unit tests cover the new logic
For each new branch in the code, look for a corresponding test case. Coverage percentage alone is misleading — a 90% line-coverage PR can still skip the actual conditional logic. Read the test names and assertions, not just the coverage delta.
-
Check integration coverage for API changes
If the PR changes an HTTP endpoint, gRPC method, or message contract, an integration test should exercise the request/response shape end-to-end. Unit tests with mocked clients miss serialization bugs and middleware ordering issues.
-
Review edge cases and error paths
Common gaps: empty input, null/None, max-length input, unicode/emoji, concurrent calls, network timeout, partial failure, retry behavior. Ask: what happens if the downstream call returns 500? Is the error logged with enough context for an on-call to triage at 3am?
Design and Architecture
-
Assess fit with the service architecture
Does this change belong in this service? PRs that introduce cross-service coupling (a billing concept leaking into the auth service, a UI module reaching into a backend repo) are architecture smells. If unsure, loop in the service owner via CODEOWNERS or a tech lead.
-
Check for duplicated functionality
Grep the codebase for the new utility's name, signature, or core logic before approving. Reinvented date parsers, retry helpers, and HTTP client wrappers proliferate when reviewers don't ask. If a similar helper exists, push back unless the author has a specific reason not to use it.
-
Evaluate scalability and SOLID adherence
Watch for N+1 queries on list endpoints, unbounded loops over user input, in-memory aggregations that will OOM at 10x scale, and god-classes that violate single responsibility. "It works for our current data size" is not a passing answer.
-
Flag whether the PR includes a database migration
Look for files under db/migrate, migrations/, Alembic, Flyway, golang-migrate, or schema.rb/schema.prisma changes. A schema change deserves its own review pass — these are the highest-blast-radius changes in the diff.
Collects list -
Review migration for locks and reversibility
On Postgres, ADD COLUMN with a non-null default rewrites the table under exclusive lock — fine on 10K rows, catastrophic on 50M. Require CREATE INDEX CONCURRENTLY, batched backfills, and a documented rollback. Confirm the migration is reversible or that an explicit forward-only decision is captured in the PR description.
Security and Dependencies
-
Scan the diff for hardcoded secrets
API keys, database URLs, JWT secrets, AWS access keys, internal hostnames in test fixtures. GitHub secret scanning + gitleaks/trufflehog in pre-commit catch most cases, but a human pass on the diff is the last line of defense. If a secret lands on main, rotation alone doesn't fix it — the value lives in git history forever without filter-repo or BFG.
-
Verify authz on new endpoints
Every new endpoint needs an explicit authorization check, not just authentication. Confirm the user can only access resources they own (IDOR protection), and that input is validated before hitting the database (SQL injection, command injection, OWASP Top 10). "The frontend prevents this" is not an authorization model.
-
Flag newly added third-party dependencies
Check package.json / Gemfile / go.mod / pyproject.toml diff for new entries. Each new dep adds maintenance surface, license obligations, and supply-chain risk. Worth scrutiny even if it's a one-line install.
Collects list -
Check new dependencies for CVEs and license fit
Run Snyk, npm audit, bundler-audit, or your SCA tool against the new dep tree. Verify the license is compatible with the product (no AGPL surprises in a closed-source SaaS, no SSPL gotchas). A package with 200 weekly downloads, no recent commits, and a single maintainer is a supply-chain liability — push back.
Approval and Merge
-
Record the final review decision
Use the platform's review action — Approve, Request changes, or Comment. Don't approve with unresolved blocking comments; either resolve them or use "Request changes" so the PR can't be merged. Reviewer notes capture context (what you focused on, what you skipped) for the audit trail and for the next reviewer if approval is delegated.
Collects list Collects paragraph -
Verify the merge strategy matches repo policy
Confirm squash vs. rebase vs. merge-commit matches the repo's branch protection rules. Confirm CODEOWNERS approvals are satisfied. If the commit message will become the squash message, make sure it's not "fix typo (#1234)" — clean it up before merge so the main-branch history stays readable.
-
File follow-up tickets for deferred feedback
When the decision is "Request changes", capture the blocking items as PR comments and any non-blocking items as tickets in Jira/Linear linked from the PR. Avoid the pattern where deferred feedback lives only in PR threads — those become unsearchable once the PR is closed.
Use this template
Copy it to your account, customize the steps, and run it with your team in minutes.
Browse hundreds of free templates across every team and industry.
Back to template libraryRun Code Review Checklist with your team
Customize the steps, assign roles, set a schedule, and keep a complete record for every run.