Skip to content
Alexander

Code review interview: how to review someone else's code

The order in which to read unfamiliar code in an interview, how to phrase comments out loud and set priorities, with a TypeScript snippet walkthrough.

Code review interview: how to review someone else's code

Instead of an algorithm task, you're shown forty lines of someone else's code and told: "Imagine this is a pull request from a colleague. What would you say?" The format looks easy, since there's nothing to write. But three minutes in, you catch yourself discussing a variable name while the interviewer waits for you to notice the SQL injection at the very top of the function.

Review skills are tested both with a question and in practice. "What do you pay attention to when you do a code review?" was asked at 17 companies in the Sobes bank. The practical version needs different preparation than live coding: here you're assessed on what you noticed, in what order, and how you said it.

What the interviewer evaluates

  • Whether you see serious problems or only style.
  • Whether you can tell "this will break production" from "I'd write it differently."
  • How you phrase comments: people will work with you and read your comments every day.
  • Whether you ask about context before criticizing.

The order to read in

First spend a minute reading without commenting and say out loud what the code does. If that's unclear, ask: "Is this an HTTP request handler or an internal function? Who calls it?" The answer determines which problems count as serious.

Then go down the list:

  1. Correctness. Does the code do what it claims on ordinary data.
  2. Edge cases. Empty input, null, zero, a repeated call, two simultaneous requests.
  3. Security. User input in queries, access rights, secrets in code and logs.
  4. Readability. Names, function size, how many things it does at once.
  5. Performance. Queries in a loop, extra copies, work that could be skipped.
  6. Tests. Whether they exist and what exactly should be covered.

The order follows the cost of a mistake. If you only have time for half of the list, you'll still have named the most dangerous things.

An example

A function applies a promo code to a cart:

async function applyPromo(userId: string, code: string) {
  const promo = await db.queryOne(
    `SELECT * FROM promos WHERE code = '${code}'`
  );
  const user = await db.users.findById(userId);
  if (promo.expiresAt < new Date()) {
    return { ok: false };
  }
  const discount = user.cart.total * promo.percent / 100;
  user.cart.total = user.cart.total - discount;
  promo.usedCount++;
  await db.users.save(user);
  await db.promos.save(promo);
  return { ok: true, total: user.cart.total };
}

Here is how the review might sound:

Let me check that I understand it first: the function finds the promo code, checks the expiry, reduces the cart total, and increments the usage counter. The most serious issue is in the first query: the promo code is interpolated into SQL as a string, which is an injection. It needs a parameterized query, and I'd call this a blocker.

Second: if there's no promo with that code, promo will be null and the function will crash on the expiry check. It needs an explicit "promo not found" branch with a clear error.

Third: the promo can be applied to the same cart any number of times, and each call reduces total again. I'd store the applied promo separately and compute the final amount from the original sum.

Fourth: the two saves run without a transaction. If the second one fails, the discount is applied but the counter isn't incremented. And with two simultaneous requests, usedCount++ will lose an update. This needs a transaction and an atomic increment in the database.

After that you can move on to the less urgent items: money is calculated in floating point, so a 15% discount will produce fractional cents with a tail; the usage limit isn't checked even though the counter exists; { ok: false } carries no reason for the refusal. And tests at the end: an expired promo, a nonexistent one, a repeated application.

There isn't a word about variable names in this review, and that's correct. Next to an injection and a race condition, they aren't worth the time.

How to phrase comments

A comment is easiest to build from three parts: what's wrong, what it leads to, what you propose. "This is bad" contains none of them.

Say the priority out loud. Three levels are enough:

  • Blocker: this can't be merged (injection, data loss, crash).
  • Should fix: it works, but will break in a predictable case.
  • Author's call: style, names, an alternative way to write it.

Where you aren't sure, ask instead of asserting:

There's no usage limit check here. Is that deliberate because it's checked higher up the stack, or an oversight?

A question is more honest than a statement when you can't see the rest of the code, and in a real review it lands much more calmly. Interviewers note that separately.

If something in the code is done well, say so in one sentence. A review with twenty complaints and not a single "this part is clear" is hard to read on a team.

If the language is unfamiliar

Say so right away and propose how you'll work:

I haven't written Kotlin. I can read the syntax but may not know the idioms. Let me review the logic and the data handling, and I'll ask about language specifics.

Most serious problems don't depend on the language: unvalidated input, a missing transaction, a query in a loop, a swallowed error, a race. Go down the same list and ask about what you don't know: "Does this call throw an exception here or return null?" Guessing is worse. A confident wrong statement about the language costs more than an honest question. More on reasoning out loud without a ready answer is in Don't know the answer in an interview?.

Common mistakes

  • Starting with style and never reaching the bugs.
  • Producing a list of fifteen items with no priorities.
  • Rewriting the code instead of reviewing it. You were asked to assess it, not to show your own version.
  • Criticizing the author: "who writes like this." A comment is always about the code.
  • Reading silently for five minutes. Thinking aloud matters here as much as in live coding.

The skill is useful from the other side too: when you defend a take-home assignment, your own code is the one under review.

How to practice

The six-item list can be memorized in one go, while the habit of following it takes about a dozen reviews. The code trainer has code review tasks: you see a snippet, write your comments, and an AI check scores the answer against the author's rubric. It also has output prediction tasks, which train careful reading.

Another way: open a pull request of your own from six months ago and review it out loud using the list, with priorities. By now that code feels like someone else's.