"Review this PR / diff"
📖 Walk me through it — plain English
This lesson is not an algorithm to memorize — it is an interview round. Instead of writing code from scratch, you are handed a PR (a "pull request" — a proposed batch of code changes someone wants to merge into the project) or a diff (the highlighted before/after showing exactly which lines were added or removed) and asked to review it. The interviewer is watching two things at once: what you notice (real bugs, security holes, weak tests) and how you communicate (clearly, kindly, prioritized). The lesson's headline warning is the key idea: a correct review delivered badly still fails the round. Being right is only half the score.
Here is the analogy. Imagine you are a copy-editor handed a friend's college essay. A bad editor scribbles "make this better" in the margin and circles every comma. A great editor first asks "does the argument actually hold together?" (correctness), then "is anything here libelous or plagiarized?" (security/integrity), then checks the citations actually support the claims (tests), and only at the very end mentions "tiny thing — you wrote 'their' instead of 'there' on page 3" and labels it as minor so it doesn't drown out the big note. Same essay, same red pen — completely different value. Code review is that, for code.
So how do you approach the round? Work top-down through the pass order in the lesson, highest-signal first, because that is where the points are:
- Correctness first. Does the code actually do what it claims? Hunt edge cases, off-by-one errors (a loop that runs one step too many or too few — e.g.
i <= nwhere it should bei < n), and error paths nobody tested. - Security and data integrity. Injection (untrusted input slipping into a SQL query, HTML page, or shell command), missing authorization checks, or race conditions (two things touching shared data at once and corrupting it).
- Tests. Does the test really exercise the new change, or just rubber-stamp the easy "happy path" where nothing goes wrong?
- Design and readability. Does it fit the codebase — good names, no dead code, no leaky abstractions?
- Style nits, dead last. Prefix them literally with "nit:" so the author knows it is optional and it doesn't block the merge.
And how you say it matters as much as what you say. Be specific and actionable — not "this could be cleaner" but "extract lines 40–55 into a helper named computeTotal; it's used twice." Ask before assuming ("why X over Y here?") so you either learn the author's reason or gently surface that there wasn't one. Put the one real blocker in a top-level comment; don't bury a correctness bug under fifteen nits. And praise the genuinely good catches — reviews are also a calibration signal about your judgment.
One special case the lesson flags: the Meta-style round of reviewing AI-generated code. The company deliberately seeds machine-written code with planted bugs, and the AI assistant in the panel is told not to reveal them — so asking "what's wrong with this?" gets you nothing. You actually have to read. The classic planted failures: hallucinated APIs (a method that sounds real but doesn't exist in that runtime, like arr.toSorted() on an old Node), made-up imports (OrderedHashSet), type drift (seconds vs. milliseconds, 0-indexed vs. 1-indexed mixed in one function), hidden O(n²) cost (checking word in seen_list instead of a set), a graph walk with no "visited" set that loops forever on a cycle, and silently swallowed errors (catch (e) {} with no log).
The takeaway the lesson calls "the senior tell": read the test before the implementation. If the test still passes on the broken version of the code, then the test is the real bug — it isn't checking what matters. Junior reviewers dive straight into the implementation and miss this; leading with the test is what signals seniority.
Some teams replace a coding round with a code review. They want to see what you notice + how you communicate. Both matter — a correct review delivered badly still fails the round.
First, the vocabulary — every term, defined
Before the strategy, lock down the words, because the interviewer will use them as if you already know them. None of this requires prior team experience — it is just naming the parts.
- Code review — the practice of one engineer reading another engineer's proposed change before it merges into the shared codebase, to catch problems early and keep quality high. It is the most common gate in professional software: almost nothing ships without at least one other human (or now, a model) reading it.
- The review round — the interview format where you play the reviewer. You are given a finished change and asked to talk through it aloud: what is good, what is wrong, what you would ask the author, and whether you would approve. There is usually no coding from scratch; the skill on test is judgment, not typing speed.
- PR (pull request) — a bundled set of changes (often across several files) that an author "requests" be "pulled" into the main branch. It comes with a title, a description of intent, the file changes, and a discussion thread. (GitLab calls the same thing a "merge request" — MR.)
- Diff — the line-by-line before/after view of the change. Removed lines are marked
-(usually red), added lines+(usually green), unchanged context lines are shown plain for orientation. Reviewing the diff, not the whole file, keeps you focused on what actually changed. - What to look for — the standard checklist, in priority order: correctness (does it produce the right result?), edge cases (empty input, zero, negatives, nulls, the very first and very last element, duplicates, huge input), readability (can the next person understand it in 30 seconds?), security (can untrusted input cause harm?), performance (does it scale, or is it secretly slow?), and tests (is the new behavior actually verified?).
- Severity levels — how badly a problem matters. A blocking issue must be fixed before merge (a real bug, a security hole, a missing critical test). A nit (short for "nitpick") is a minor, optional suggestion (naming, formatting, a slightly tidier line) that should never hold up the change. Calling out which is which is the single most important communication move in a review.
- Giving feedback kindly + specifically — comment on the code, never the person ("this loop re-reads the file each iteration," not "you didn't think this through"). Be concrete enough to act on, and where possible suggest the fix. Kind and specific are not in tension — specificity is a kindness, because vague criticism is the frustrating kind.
A plain-English on-ramp: you already do this
If "code review" feels intimidating, reframe it. You have reviewed things your whole life. When a friend texts you a draft of an important message and asks "does this sound okay?", you instinctively run a triage: Will it cause a real problem? (you stop them from sending something rude — that is the blocking bug). Is anything risky? (you flag that they CC'd the wrong person — that is the security issue). Does it do the job? (you check it actually asks for what they want — that is correctness and tests). And only then, could it read better? (you suggest a smoother phrasing — that is the nit). You also know not to lead with "this is bad" — you start with what works, then raise the one thing that matters, then mention the small stuff last. A code review is exactly that instinct pointed at a diff. The interviewer is not testing whether you can recite a checklist; they are testing whether you can read carefully, separate the big problem from the small ones, and say so in a way that makes the author want to fix it.
- Correctness — does it do what it claims? Edge cases? Off-by-one? Error paths?
- Security / data integrity — SQL/HTML/shell injection, missing auth checks, race conditions on shared state.
- Tests — does the test actually exercise the change? Or just assert the happy path?
- Design / readability — does the change fit the codebase? Naming, dead code, leaky abstractions.
- Style / nits — last, and explicitly labeled "nit:" so it doesn't block.
The six things to look for, expanded
The pass order above is the skeleton. Here is what each pass actually means when your eyes are on real code, with the concrete questions to ask yourself.
Trace the logic by hand on a tiny input. Does the result match the description in the PR? Watch for inverted conditions (< vs <=), wrong default values, and code that returns early before doing its job.
Empty list, single element, all-duplicates, zero, negatives, null/None, the first and last index, and "what if the input is enormous?" The boundary is where bugs hide.
Could a teammate understand this in 30 seconds? Are names honest (total, not x)? Is there dead code, a comment that lies, or a 40-line function doing five jobs?
Does untrusted input reach a query, page, or shell unescaped (injection)? Is there an authorization check on the sensitive action? Are secrets hard-coded? Are errors leaking internals to the user?
Is there a hidden nested loop (O(n²)) where a set/map would make it O(n)? A query inside a loop (the "N+1" problem)? Re-reading a file or re-sorting on every call? Flag it only if the data can grow.
Is the new behavior actually asserted? Would the test fail if you broke the code? Are the edge cases above covered, or just the one easy "happy path"?
Worked review: a small, smelly snippet
Here is the part that turns the checklist into a skill. Below is a tiny, realistic function someone has put up for review — "given a list of orders, return the average order total for a given customer." It runs. It even has a test that passes. Read it the way a reviewer would, top to bottom, before scrolling to the findings.
# PR: add average_order_total(orders, customer_id)
def average_order_total(orders, customer_id):
seen = []
total = 0
count = 0
for o in orders:
if o["customer"] in seen: # dedupe customers we've counted
continue
seen.append(o["customer"])
if o["customer"] == customer_id:
total = total + o["amount"]
count = count + 1
return total / count
# the test that "passes"
def test_avg():
orders = [{"customer": 1, "amount": 100}]
assert average_order_total(orders, 1) == 100
It looks innocent and the test is green. Now the review, organized by severity — blockers first, nits dead last, each with a suggested fix phrased the way you'd actually write it in the thread.
- [BLOCKING · correctness] The
seendedupe logic is wrong for this function's job. It skips every order after the first one per customer, so a customer with three orders is averaged over just one. The result isn't an average of their orders at all. Suggested fix: drop theseenbookkeeping entirely — we want to sum all of this customer's orders, not one per customer. Just filter ono["customer"] == customer_idand accumulate. - [BLOCKING · edge case]
return total / countdivides by zero whencount == 0— i.e. the customer has no orders, orordersis empty. That is a crash (ZeroDivisionError) on a completely ordinary input. Suggested fix: decide and document the contract — return0, returnNone, or raise a clear error — then guard:if count == 0: return None. - [BLOCKING · tests] The test only covers one customer with one order — the exact case where the bugs above are invisible. It would pass even on the broken version, so it verifies nothing that matters. Suggested fix: add cases for multiple orders per customer (the real average), a customer with zero orders (the divide-by-zero), and an empty list. Run each against the current code and watch it fail — that proves the test is doing its job.
- [NON-BLOCKING · performance]
o["customer"] in seenscans a list each iteration, making the loop O(n²). Once the dedupe is removed this disappears; if any membership check survives, use asetfor O(1) lookups. - [NON-BLOCKING · robustness]
o["amount"]/o["customer"]assume every order dict has those keys; a malformed record raisesKeyError. Optional: use.get()with a default or validate upstream, depending on how trusted the input is. - nit: readability
total = total + o["amount"]reads fine astotal += o["amount"], andcount = count + 1ascount += 1. Purely cosmetic — does not block.
Notice the shape of a good review here: three blocking items, all of which are real problems that would burn a user, are stated first and clearly; the performance and robustness notes are flagged as non-blocking; and the cosmetic suggestion is labeled nit: so it cannot be mistaken for a demand. A reviewer who instead opened with "use += here" — and never noticed the division by zero — would fail the round despite being technically correct about the nit.
How to phrase it (kind + specific)
The same finding can land as helpful or as hostile depending on wording. The pattern that works: name the issue on the code, explain the why/cost, and offer a fix or a question.
"This is broken." · "Why would you do it this way?" · "Cleaner please." · "Did you even test this?"
"count can be 0 when the customer has no orders, which divides by zero — can we return None and add a test for the empty case?" · "Would a set here drop this from O(n²) to O(n)?"
"Why did you choose X over Y here?" If the author had a reason, you learn. If they didn't, the question surfaces it.
Bad: "this could be cleaner." Good: "extract lines 40-55 into a helper named computeTotal; it's used twice."
Suggest the diff. ```suggestion blocks in GitHub get accepted with one click.
Don't bury a correctness bug under 15 nits. Top-level comment for the blocker. Inline for the rest.
When someone catches a subtle bug, names a variable well, or simplifies a tangled bit — say so. Reviews are also calibration signal.
If the diff is fundamentally wrong (wrong approach, scope creep), don't line-comment 30 nits. Pair-program / chat / propose a rethink.
Meta now seeds AI-generated code with specific failure modes and asks you to find them. The model in the panel is system-prompted not to surface them — "what's wrong with this?" returns nothing useful. You have to read.
arr.toSorted() on old Node. str.replaceAll on legacy targets. Methods that look plausible but don't exist in this version/runtime.
i <= n where it should be i < n. Especially when the model rewrote bounds without re-checking.
int silently cast to double, bigint dropped to number, ms vs seconds, 0-indexed vs 1-indexed mixed in one function.
for word in words: if word in seen_list: — looks O(n), actually O(n²). Set/list confusion is a top model error.
Graph traversal that loops forever on cycles. Model wrote BFS but forgot the seen-check.
try { x } catch (e) {} with no log. Code "works" until it doesn't — and you have nothing to debug.
from collections import OrderedHashSet. Real-sounding but doesn't exist. Run before trusting.
Binary search on an unsorted array. Two-pointer on negative numbers. The pattern is right; the precondition isn't.
What interviewers reward — and the pitfalls
- Finds the real bug, not just surface style.
- Reads the test first and questions whether it would fail on broken code.
- States severity explicitly (blocking vs nit) and leads with the blocker.
- Phrases feedback as kind, specific, fixable — comments on code, not the person.
- Asks the author's intent before declaring something wrong.
- Names what's good, so the review reads as collaboration, not a gauntlet.
- Only style nits. Twenty formatting comments while the divide-by-zero sails through. You looked busy and caught nothing that matters.
- Harsh tone. "Did you even test this?" Even when you're right, dismissiveness reads as someone nobody will want to be reviewed by.
- Missing the real bug. The whole round is built around a planted correctness or security flaw; not finding it is the failing grade no matter how polished the rest is.
- No prioritization. Dumping ten unlabeled comments so the author can't tell the blocker from the cosmetics.
- Trusting green tests. "Tests pass, approve." A weak test passing proves nothing.
Takeaway: a code review round scores two axes — what you notice and how you communicate. Read the test first; trace correctness on a tiny input; hunt edge cases, security, and hidden O(n²) before style. Separate blocking issues from nits and say which is which. Phrase every comment on the code, kindly and specifically, with a fix or a question. The fastest ways to fail are catching only nits, sounding harsh, or missing the one real bug the round was built around.