Frontend Code Review Interview Round
How senior loops use a PR: correctness, a11y, a race, a secret in the client, and how you talk to a junior author without being cruel.
- interview
- code-review
- senior
A frontend code review interview round sometimes skips a fourth algorithm and drops a PR in front of you instead. The miss is nitpicking import order while a Pay handler uses innerHTML, a div click, and a fetch with no abort. Review rounds score whether you protect users and the next reader, and whether you can say that to a junior author without performing dominance.
You are not the compiler. You are the teammate who catches the bug that would have shipped on Friday.
Key takeaways
- Sort findings: correctness, then users (a11y, XSS, data loss), then maintainers (types, tests, names).
- One high-severity comment with a repro beats twelve nits.
- Ask questions when you might be wrong. Be direct when you are not (secrets, XSS).
- Offer a patch shape, not a rewrite of their PR in your style.
Correctness first, then users, then maintainers
Read the diff once for story, once for bugs. Talk in this order so the interviewer hears judgment, not a stream of comments.
| Priority | Question | Examples |
|---|---|---|
| P0 Correctness | Does the feature do the wrong thing? | Double pay, stale search results, off-by-one, broken loading |
| P1 Users | Can a person be harmed or blocked? | XSS, keyboard trap, unlabeled button, secret in the bundle, lost data |
| P2 Maintainers | Will the next edit fail? | any, no test, copy-paste hook, effect without cleanup |
| P3 Nits | Style | quotes, import sort, optional chaining taste |
If time is 30 minutes, you may never reach P3. Say that: “I am stopping at user-facing issues.”
A PR you might be handed
Treat the following as a stand-in, not a company question. Read it like a diff.
function PayButton({ amount, user }) {
const [html, setHtml] = useState('');
async function pay() {
const res = await fetch('/api/pay?amount=' + amount);
const body = await res.text();
setHtml(body);
}
return (
<div>
<div onClick={pay}>Pay {amount}</div>
<div dangerouslySetInnerHTML={{ __html: html }} />
<p>{user.apiKey}</p>
</div>
);
}
Walk it in the priority order.
Correctness
GETfor a charge. Caches, prefetch, and retries can replay it. Needs a mutating method and an idempotency key.- No disable / in-flight guard. Double click is two requests.
- No
res.okcheck. A 402 HTML error page becomes “success UI.” amountconcatenated into a query string: encoding, logging, and cache.
Users
div+onClick: not a button, no keyboard, no name. See keyboard accessibility.dangerouslySetInnerHTMLon a network body: XSS. Even “trusted” payment HTML is a sink.user.apiKeyin the document: secrets in frontend bundles. If it rendered, it already leaked.
Maintainers
- No
AbortControllerif the component unmounts mid-pay (AbortController). - No types on
amount(string"1,000"vs number). See TypeScript interview. - No test for double-submit or error. See testing interview questions.
A comment you could actually paste:
P0: `/api/pay` is fetched with GET and the raw body is injected as HTML.
Repro: click Pay twice, or serve `<img src=x onerror=alert(1)>` as the response.
Please: POST with an idempotency key, `res.ok` + JSON, text content (not HTML),
and a real <button disabled={pending}>. apiKey must never render.
That is specific, ranked, and has a next action. It is not “this is not clean.”
Races and abort
Search boxes and pay sheets share a bug: response N+1 arrives before N. Review for:
- missing
AbortControlleron the previous request - ignoring
AbortErrorvs treating it as a user-facing failure awaitinuseEffectwithout a cancelled flag or signal- setting state after unmount (warning plus a leak of intent)
useEffect(() => {
const controller = new AbortController();
fetch(`/api/search?q=${encodeURIComponent(query)}`, { signal: controller.signal })
.then((r) => r.json())
.then(setItems)
.catch((err) => {
if (err.name === 'AbortError') return;
setError(err);
});
return () => controller.abort();
}, [query]);
If the PR used a module-level let lastId instead, say why a signal is clearer, but do not bike-shed a working counter unless it is wrong.
a11y in reviews
You do not need a full WCAG audit in 20 minutes. You do need:
- Controls are
<button>/<a>/<input>, not clickabledivs - Visible label, or
aria-labelthat matches purpose - Errors as text, tied to the field (accessible forms)
- Focus on the new dialog, restore on close (accessible modals)
- Color not the only error signal
Talking points: accessibility interview.
Secrets and XSS
Frontend review is where XSS and leaked tokens still slip. Sinks: innerHTML, dangerouslySetInnerHTML, javascript: URLs, eval, unsanitized href. Tokens: anything in JS bundles, localStorage “because HttpOnly was hard,” debug console.log(user). Map: security interview talking points, XSS prevention checklist.
If you see a secret, that comment is P1 even if the feature “works.”
How you talk to a junior author
Interviewers listen to tone. Cruelty is a no-hire on some loops. Performative softness that hides a P0 is also a miss.
| Instead of | Say |
|---|---|
| “This is wrong, rewrite it.” | “GET on pay will replay. Can we POST + idempotency key?” |
| “Why didn’t you use Redux?” | “State looks local and that is fine. The bug is the race.” |
| 20 nits on naming | One nit max, after P0/P1 |
| “a11y: add aria” | “This clickable div is not a keyboard button. <button> gives that free.” |
| Silence on XSS | Direct: “This HTML sink is exploitable. We cannot merge it.” |
Procedure:
- Restate what the PR is trying to do (they may correct you).
- P0 bugs with repro.
- P1 users.
- One improvement (test, type, abort).
- What you would not block on.
Praise one thing that is actually good (“nice isolation of the hook”) so the author can hear the rest. Fake praise is obvious.
Footguns (as the reviewer)
- Rewriting the PR in the comment box
- Ignoring tests that already fail
- Flagging
letvsconstbefore XSS - Assuming the author cannot see browser APIs you like
- Saying “ship it” because you are afraid to be direct
Interview out-loud answer
“I would read for behavior first. On this Pay button I would block on GET-plus-HTML injection, double submit, and the rendered apiKey. Then I would ask for a real button, disabled pending state, JSON errors as text, and an abort on unmount. I would leave style nits off the first pass. I would write one P0 comment with a repro so a junior author knows what to change, not that I am clever.”