How to Give and Receive Code Reviews That Actually Improve Code
Code reviews are simultaneously the most valuable and most painful part of collaborative software development. Done well, they catch bugs, spread knowledge, maintain quality, and level up junior engineers. Done poorly, they slow down delivery, create resentment, and become a rubber-stamping formality.
After participating in thousands of code reviews over my career — as author, reviewer, and occasionally as the person mediating a heated comment thread — here is what I have learned about making reviews actually useful.
The Purpose of Code Review
Before diving into tactics, align on purpose. Code reviews serve multiple goals:
Correctness — catching bugs, edge cases, and logical errors before they reach production. This is the obvious goal, but honestly not the highest-value one for experienced teams.
Knowledge sharing — ensuring more than one person understands every part of the codebase. If only Alice understands the payment service, Alice cannot go on vacation. Reviews distribute knowledge.
Consistency — maintaining consistent patterns, conventions, and quality across the codebase. Without reviews, five developers produce five different coding styles in the same project.
Mentoring — helping junior developers learn patterns, avoid pitfalls, and understand the reasoning behind architectural decisions. This is often the highest-value outcome of reviews.
Writing Reviewable Pull Requests
The author's job is to make the reviewer's job easy. An impossible-to-review PR gets rubber-stamped, which defeats the purpose.
Keep PRs small. Under 400 lines of meaningful changes (excluding generated code, test fixtures, and lock files). Large PRs get superficial reviews because reviewer attention degrades linearly with size. If your feature requires 2000 lines of changes, break it into a sequence of smaller PRs that build on each other.
Write a useful description. Not "fixed the thing" — explain what the PR does, why it does it this way, what alternatives you considered, and what to pay attention to during review. Link to the ticket. Include screenshots for UI changes. If there are areas you are uncertain about, say so.
Self-review before requesting review. Read your own diff as if someone else wrote it. You will catch formatting issues, debug logging, commented-out code, and TODOs that you forgot to address. Self-review takes 5 minutes and saves the reviewer from pointing out obvious issues.
Separate refactoring from feature work. A PR that renames 50 variables AND adds a new feature is hard to review because the reviewer cannot tell which changes are mechanical refactoring and which are new logic. Ship the refactoring as a separate PR first.
Reviewing Effectively
What to Look For
Start with the big picture. Before reading individual lines, understand the overall approach. Does the architecture make sense? Are the abstractions at the right level? Is the approach consistent with how the rest of the codebase handles similar problems?
Then check for correctness. Does the code handle edge cases (null values, empty arrays, concurrent access)? Are error conditions handled? Does the logic match the ticket requirements? Are there off-by-one errors, race conditions, or security issues?
Finally, check for clarity. Will the next developer who reads this code understand it without the PR description? Are variable names descriptive? Is the control flow straightforward? Could any complex sections benefit from a comment explaining why (not what)?
What NOT to Look For
Style issues that a linter should catch. If you are commenting on indentation, bracket placement, or import order, configure a linter and stop wasting human attention on machines' work.
Personal preferences that do not affect correctness or clarity. "I would have used reduce instead of a for loop" is not useful feedback unless the for loop is actually less clear. Different developers have different styles, and that is fine.
Hypothetical future requirements. "What if we need to support X later?" is only valid if X is on the roadmap. Do not ask authors to over-engineer for scenarios that may never materialize.
How to Communicate
Be specific. "This could be clearer" is not actionable. "I did not understand what this variable represents — could you rename it to something like pendingTransactionCount?" is actionable.
Distinguish between blocking and non-blocking comments. Use a prefix or convention: "nit:" for suggestions that do not block approval, "question:" for clarifications, no prefix for issues that need addressing. This tells the author which comments they must address and which are optional.
Explain the reasoning. Do not just say "use a Map instead of an Object here." Explain why: "A Map would be better here because the keys are user-provided strings and could collide with Object prototype methods like constructor or toString." The author learns something, and the reasoning can be debated if it does not apply.
Ask questions instead of making demands. "Why did you choose to fetch the data here instead of in the parent?" is less confrontational than "Move the data fetching to the parent" and might reveal context you are missing.
Acknowledge good work. If the PR has a clever solution, clean abstractions, or particularly good test coverage, say so. Reviews that only point out problems are demoralizing over time.
Receiving Reviews Gracefully
Separate Code From Identity
The hardest part of receiving reviews is not taking feedback personally. The reviewer is critiquing your code, not your competence. This is emotionally difficult — we invest ourselves in our work — but it is essential for productive collaboration.
When you feel defensive about a comment, pause before responding. Ask yourself: "Is this feedback valid regardless of how it makes me feel?" Often the answer is yes, and your initial defensive reaction was just ego.
Respond to Every Comment
Even if the response is "done" or "good point, fixed." Unresolved comments create ambiguity about whether the author saw and addressed the feedback. Respond promptly — lingering reviews slow down the entire team.
If you disagree with feedback, explain your reasoning calmly. "I considered that approach but chose this one because [specific reason]." This opens a dialogue. Sometimes you will convince the reviewer; sometimes they will convince you. Both outcomes are productive.
Do Not Over-Explain
If a reviewer asks "why is this function 80 lines long?" the answer should not be a 500-word justification. The answer should be refactoring the function into smaller pieces, or a brief explanation of why the length is necessary and cannot be reduced. Over-explaining is a sign that the code is not self-explanatory.
Building a Healthy Review Culture
Establish Norms
Document your team's review expectations. How long should reviews take? (We target 24 hours for initial review.) How many approvals are needed? (One for most PRs, two for infrastructure changes.) What qualifies as a blocking issue? (Correctness bugs, security issues, performance regressions.)
Rotate Reviewers
Do not let the same senior developer review everything. Rotate review assignments so knowledge spreads evenly and junior developers get practice reviewing. Junior developers often catch issues that seniors miss because they read the code more carefully — they are learning, not skimming.
Measure and Improve
Track review metrics: time from PR open to first review, time from first review to merge, number of review cycles. If PRs sit unreviewed for days, you have a capacity problem. If PRs require 5+ cycles of review, you have an alignment problem.
Retrospect on reviews quarterly. Which reviews caught real bugs? Which were pure nitpicking? Are there patterns in the feedback that suggest a team training need?
The ROI of Good Reviews
Teams with healthy code review practices have fewer production incidents (bugs caught before merge), lower onboarding time (new developers learn patterns from reviews), more consistent codebases (conventions enforced socially), and higher retention (developers who feel heard and mentored stay longer).
The investment is significant — reviews consume 10-20% of engineering time. But the return in quality, knowledge sharing, and team health is worth far more than that 10-20% would produce if spent on additional feature work.
Written by AIDToolStack Editorial
The AIDToolStack editorial team reviews developer tools, AI products, and MCP servers with hands-on testing and honest assessments. We use the tools we review every day in production.
References & Further Reading
Related Posts
Sponsor Our Newsletter
Reach thousands of developers who are actively evaluating AI tools, MCP servers, and dev infrastructure. Our weekly newsletter goes to engaged technical decision-makers.
All sponsored content is clearly labeled per our editorial policy.