Picture this: you open your pull request on a Monday morning, proud of the cleanest code you've written in months. By afternoon, a single comment has you dreading every future deploy. Not because the feedback was wrong — it wasn't — but because of how it was delivered. "This is needlessly complex. Why would you do it this way?" Seven words. Zero actionable guidance. And somehow your whole week tastes worse for it.
Code review is the highest-leverage ritual in any engineering team. Done well, it ships better software, grows engineers faster, and quietly cements a culture of trust. Done poorly, it burns people out, slows delivery, and drives your best engineers toward teams where they don't feel like they're defending themselves every pull request. This guide is about doing it well — concrete phrasings, a reviewer's checklist, the habits that quietly turn reviews toxic, and what to do when you're the one receiving the comments.
Code review is culture, not just bug-catching
There is a common misunderstanding about what code review is for. The technical goal — finding bugs before they reach production — is real and valuable. But it's only part of the story.
Every comment you leave teaches something. It teaches the author about the codebase, about tradeoffs, about the standards the team cares about. Over hundreds of reviews, you are the team's collective memory, made explicit. New engineers learn what "good" means here by reading your comments. Junior engineers become seniors by internalising the questions reviewers consistently ask. Seniors stay sharp by articulating the "why" behind their instincts.
Reviews also set norms invisibly. A team that consistently leaves kind, specific, curious comments becomes a place where it's psychologically safe to ship something imperfect and learn from it. A team that leaves blunt, dismissive, or cryptic comments becomes a place where people over-engineer in silence to avoid criticism, delay opening PRs, or stop asking questions.
When engineers dread opening pull requests, they batch work into larger and larger changesets to minimise the number of reviews. Larger PRs are harder to review well, so review quality drops, feedback becomes harsher, and the cycle deepens. The antidote isn't less rigour — it's more kindness alongside the rigour.
None of this means code review should be soft or toothless. The most effective reviewers are deeply honest. They flag problems clearly. They hold the line on things that genuinely matter. But they do it in a way that leaves the author feeling like they've gained a collaborator, not faced a judge.
What to actually review — and what to leave alone
One of the most common review mistakes is reviewing everything with equal weight. The reviewer who blocks a PR over a missing trailing comma carries the same veto power as one flagging a SQL injection. That asymmetry erodes trust fast. Start by being clear in your own head about what genuinely matters:
- Correctness: Does this code do what it's supposed to? Are edge cases handled? Could this fail in production in a way the tests don't cover?
- Design: Does this change fit naturally into the existing architecture? Does it introduce unnecessary coupling? Is there a simpler approach that solves the same problem?
- Readability: Will a teammate who didn't write this understand it in six months? Are names clear? Is the flow easy to follow?
- Tests: Are the tests meaningful? Do they cover the happy path, the edge cases, and the failure modes? Would a test failure actually tell you something useful?
- Security: Is user input validated? Could this introduce a data leak, a privilege escalation, or an injection? (If yes, treat it as a blocker.)
What to not spend review energy on:
- Formatting and style that your linter or formatter enforces automatically. If your CI catches it, you don't need to. The human review slot is expensive — don't spend it on what a machine handles.
- Personal preferences that aren't team standards. "I would have named it differently" is not a review comment. "This name doesn't match the ubiquitous language in the rest of the domain" sometimes is.
- Architecture debates in a PR comment thread. If you have a fundamental disagreement about direction, that's a design conversation, not a code review. Have it separately and collaboratively before the next PR starts.
Before writing a comment, ask: "If the author made this exact change, would the code be meaningfully better?" If the answer is "a little" or "maybe", consider whether it's worth their time and yours. If the answer is "yes, and here's why", write it — but write it kindly.
How to phrase comments kindly — and still honestly
The single biggest lever in code review is word choice. The same concern, delivered two different ways, produces entirely different responses in the person reading it. Here's the practical toolkit:
- Ask questions instead of issuing verdicts. "Why did you choose X over Y here?" invites a conversation. "You should have used Y" closes one. The question might reveal context you didn't have — and even if it doesn't, the author arrives at the same conclusion without feeling accused.
- Explain the why, not just the what. "Can we move this logic into a service?" is weaker than "Can we move this logic into a service? Keeping it in the controller makes it harder to test in isolation, and we'd have to duplicate it if a cron job ever needs the same behaviour." The why teaches; the what just directs.
- Offer a suggestion, not just a critique. If you see a problem, show what you'd do instead — or at least the shape of it. "I'm not sure about this, but something like…" is far more useful than "this doesn't seem right."
- Praise good code explicitly. "This error handling is really clean — I'm going to steal this pattern" takes ten seconds to type and costs nothing. It anchors the author's confidence, it reinforces the pattern as a team standard, and it makes the critical comments land better when they come.
- Use "we" and "our" where honest. "Our convention is to keep this near the repository" is softer than "you've put this in the wrong place" without being less accurate.
Harsh vs kind: the same point, two ways
| Harsh (but technically correct) | Kind — and still honest |
|---|---|
| "This is wrong. You need to handle the null case." | "This will throw if user is null — can we add a guard here or document that the caller guarantees it's non-null?" |
| "Why would you do it this way? This is needlessly complex." | "I'm finding this hard to follow — is there a simpler way to express this? I might be missing some context, happy to chat if helpful." |
| "This function is doing too much. Break it up." | "This function is handling both the validation and the persistence — splitting those responsibilities might make each piece easier to test independently. What do you think?" |
| "You clearly didn't test this." | "I didn't see a test for the empty-input case — is that intentional, or shall I open a follow-up ticket for it?" |
"Use const here, not let." |
"nit: const here since it's never reassigned — makes the intent clearer." |
| "This is a security hole." | "blocking: this input is user-supplied and goes straight into the query — that's an injection risk. Can we parameterise it before merging?" |
| "Wrong abstraction." | "I wonder if this abstraction is pulling in the right direction — it feels like it might make the next feature harder. Worth a quick sync?" |
Notice what the "kind" column is not: soft, vague, or dishonest. The security issue is still flagged as a blocker. The null case still needs fixing. The function still needs splitting. Kindness here means: specific, explained, and delivered as a person talking to another person, not a judge issuing a ruling.
Nitpicks, suggestions, and blockers — label them
One of the most practical things you can do for your review culture is label the severity of every comment. Without labels, the author has to guess whether each comment means "merge is blocked" or "just a thought". That guessing costs energy and often produces the wrong answer.
A simple convention used by many teams (popularised as Conventional Comments):
- nit: a minor style or preference point — fix it or don't, the author decides. "nit: trailing comma here matches the rest of the file."
- suggestion: something that would improve the code but isn't a blocker. "suggestion: could extract this into a helper — not required for this PR though."
- blocking: the PR should not merge until this is addressed. Reserve this for correctness bugs, security issues, and clear violations of team standards.
- question: you're genuinely asking, not implying a change is needed. "question: does this run on the background worker too, or only the web process?"
- praise: explicit, genuine positive feedback. Counts. Don't skip it.
When all your blocking issues are resolved, approve with comments. This is a habit that matters. It means: "I trust you to handle the remaining nits; you don't need another round from me." It unblocks your colleague, it signals trust, and it keeps the review loop from becoming a bottleneck. The alternative — holding approval until every minor point is addressed — is one of the slow toxins in review culture.
Once all blocking items are resolved, approve and let the author decide on the rest. Holding a PR hostage to nits teaches people that reviews are an obstacle, not a collaboration. If you really care about a non-blocking point, say so explicitly — "I do care about this one, but it's your call" — so the author has full information.
The author's side: receiving feedback without taking it personally
Being reviewed is its own skill, and most engineering culture writing focuses only on the reviewer. But the author's behaviour shapes review culture just as much.
Assume good intent by default. A blunt comment is almost always a busy person who wrote quickly, not someone trying to wound you. Before you read tone into a comment, ask whether there's a neutral or positive interpretation. Usually there is.
Respond to every comment. Even if you disagree. A ✅ or "done" on implemented feedback, and an explanation on anything you're pushing back on, closes the loop and shows the reviewer their time was valued. Silence reads as passive aggression whether you intend it that way or not.
Push back when you genuinely disagree — clearly and calmly. "I see the concern, but here's why I made this choice: [reason]. Happy to discuss if you still feel strongly." This is not the same as defending every decision reflexively. The goal is to have a real conversation, not to win. Sometimes the reviewer is right. Sometimes you are. Sometimes the right answer is "let's take this to the team."
Don't over-explain your own code in the PR description. If your code needs a wall of text to justify every decision, that's a signal to improve the code, the names, or the inline comments — not to write longer PR descriptions. The best PRs are self-evident with a good description of context and intent.
Review speed and PR size — two levers with outsized impact
The quality of a code review is strongly predicted by two things that have nothing to do with the reviewer's skill: how quickly they review and how big the PR is.
Review promptly. A pull request waiting for review is a person waiting to move on. When reviews take days, work batches up — engineers open the next task to stay productive, context switches multiply, and by the time the review comes back the author has forgotten what they were thinking. A good rule of thumb: acknowledge a PR within a few hours of being tagged (even if just "I'll get to this by end of day"), and complete the review within one business day whenever feasible.
Smaller PRs get dramatically better reviews. This is well-documented and also just obvious in practice. A 100-line PR gets close attention on every line. A 1,000-line PR gets rubber-stamped, skimmed, or reviewed so slowly it becomes a source of conflict. The discipline of keeping PRs small — "one logical change per PR" is a decent heuristic — pays back more than almost any other review hygiene improvement. It also makes each PR easier to write, because you have to think clearly about what one change means.
| PR size (lines changed) | Typical review quality | Review time |
|---|---|---|
| < 200 lines | High — reviewer can hold it in working memory | 15–45 min |
| 200–500 lines | Medium — fatigue sets in on subtle issues | 45–90 min |
| 500–1,000 lines | Dropping — reviewers skim or split into sessions | Hours to a day |
| > 1,000 lines | Mostly theatre — critical issues are regularly missed | Days (or never fully) |
If a feature genuinely requires thousands of lines, consider stacked PRs: a series of small, self-contained changes that build on each other, each reviewable on its own merits. The overhead of opening more PRs is almost always worth it.
How review culture differs by team size
The right approach to code review changes substantially as a team grows. What works for three engineers often actively breaks down at thirty, and enterprise-scale teams need different tools entirely.
Solo or early startup (1–5 engineers). At this scale there may be no reviews at all, which is sometimes fine and sometimes catastrophic depending on the stakes. When you do review, the relationship is usually strong enough that blunt feedback lands well — but it's also the stage where bad habits get cemented before anyone thinks to name them. Establish the habit of labelling comment severity early; it costs nothing and becomes invaluable later.
Small growing team (5–20 engineers). This is where review culture is made. The team is large enough that the "we all know each other" shorthand starts to fade, but small enough that norms can still be set by a few influential voices. A single senior engineer who models kind, specific, labelled reviews will shift the whole team's culture within a few months. This is also when a team review guide — even one page — pays dividends: what's a blocker, what's a nit, how long should a review take.
Mid-size team (20–100 engineers). Reviews are now happening across squads who may barely know each other. The work of building trust that happens naturally in small teams has to be done deliberately. Cross-team reviews become a major source of friction if norms aren't shared. Code owners, review rotation, and lightweight review checklists help. So does making review quality a topic in engineering retrospectives — not just "did we ship fast" but "did we review well".
Large or enterprise team (100+ engineers). At this scale, review is as much a governance and compliance function as a quality function. Tooling matters enormously: automated checks, required reviewers, review SLAs, and metrics on review cycle time. The human review slot is precious and should be reserved for what only a human can do — design judgment, domain knowledge, cross-team impact. Anything a linter or static analyser can catch should be caught before the PR is opened, not in a comment thread.
Key takeaways
- Code review is culture first, bug-catching second. Every comment teaches, and patterns of comments build or erode psychological safety over months.
- Focus review energy on what matters: correctness, design, readability, tests, and security. Let your linter handle formatting.
- Kind + Specific is the goal. Explain the why, ask questions instead of issuing verdicts, and praise good code explicitly.
- Label your comments: nit / suggestion / blocking / question / praise. It removes ambiguity and respects the author's time.
- Approve with comments once blockers are resolved. Don't hold a PR hostage to nitpicks.
- As the author: assume good intent, respond to every comment, push back calmly when you disagree, and don't over-explain in the PR description.
- Smaller PRs, faster reviews. Under 200 lines gets the best attention. Stacked PRs handle large features.
- Review norms need to be deliberate — especially as teams grow past 10–20 people. Write them down, revisit them in retros.
If this resonated, the next post in this series goes wider — from how you review code to how you show up as an engineer every day: Kind Engineering.