10 min read

JS Code Review — Course Map

How the AI-era code-review interview works, and how to use this section

Read this first. It takes ten minutes. Basics such as the call stack, closures and this are in FE-10 — JavaScript Runtime Internals; this section assumes them.


The Interview Has Changed

When an AI assistant can write a working endpoint in thirty seconds, "write a function that…" tells an interviewer very little. What it cannot tell them is whether you can judge code: see that a function which works on every test will double-charge a user when two requests arrive 40 ms apart, and explain why in a way the team can act on.

So more companies now give you code you didn't write and watch you work through it. The usual tasks:

  • Find the bugs. Often planted, often in async code, often invisible to the existing tests.
  • Fix broken async code. A race, a missing await, a retry loop with no backoff.
  • Critique the design. "This works. Would you ship it? What would you change and why?"
  • Write a polyfill. Promise.all, debounce, promisify, a concurrency pool, from memory, with the edge cases.
  • Explain trade-offs out loud. Mutex or atomic SQL? allSettled or all? Chunking or a worker thread?

The common formats

FormatWhat happensWhat they score
Take-home repoA small Node/TS service (a job runner, a webhook handler, a cache) with a README that says "it has bugs; find and fix what you can in 2–3 hours". Sometimes an AI assistant is allowedWhich bugs you found, whether your fixes are minimal and tested, and the write-up explaining each one
Live "review this PR"A 100–300 line diff in a shared editor or GitHub. You comment as if it were a teammate's PRPriority (do you lead with the double charge or the variable name?), precision, and tone
"This test is flaky. Why?"A test that passes 9 times in 10, plus the code under testWhether you can name the interleaving that fails, instead of adding a sleep
Polyfill roundImplement a standard API on a whiteboard or in a REPL, then answer "what about…" questionsCorrect edge cases: empty input, ordering, errors, this, cleanup
Debug from symptoms"Since Tuesday, 1 in 200 users is charged twice." Logs and code are providedWhich checks you choose, in what order, and why

In all of them, talking is half the score. A correct fix with no explanation looks like luck. A clear explanation of the wrong interleaving followed by a corrected one looks like a senior engineer.


The One Idea Behind the Whole Section

JavaScript runs your code on one thread, but it is concurrent.

  • Concurrency means several tasks are in progress at once and take turns on one thread. Request A starts, waits for Postgres, and while it waits, request B runs.
  • Parallelism means things run at the same instant on different CPU cores. In JS that only happens with Web Workers or worker_threads (Chapter 8).

One thread does not make you safe. Every await, every callback, every event handler is a point where other code can run and change the state you just read. Your function is not one atomic step. It is a series of slices, and other requests' slices are interleaved between them:

js
async function spend(userId, cost) {
  const wallet = await db.getWallet(userId);   // ← slice 1 ends here; other requests run
  if (wallet.balance < cost) throw new Error('insufficient credits');
  await db.setBalance(userId, wallet.balance - cost);   // ← based on a read that may be stale
}

Two requests can both pass the check against the same balance. This is exactly the SELECT-then-UPDATE race you already know from Postgres, where two transactions both read 100 and both write 20. The fixes are the same family too: make the check and the write one atomic step (UPDATE … SET balance = balance - $1 WHERE balance >= $1), serialise with a lock, or detect the conflict with a version column. If you built a race-safe credit ledger, you have solved this problem once already; this section teaches you to see it in JS and say it out loud. Chapter 5 is all about it.

When you review async code, keep asking one question: "What else can run here, and what could it change?"


The Async Code Review Checklist

This is the one-page version. Each line is expanded in the chapter shown.

#Look forThe bug it signalsChapter
1A read, then an await, then a write based on the readCheck-then-act race; lost update; double spend5
2A promise that is created but not returned, awaited or caughtFloating promise: success reported early, errors unhandled3, 4
3forEach(async …), .map(async …) without Promise.allWork not awaited; errors escape4
4Promise.all over an unbounded arrayRate limits, pool exhaustion, memory spikes7
5A network call with no timeout or AbortSignalHung requests that hold sockets, locks and pool slots6
6Retries with a fixed delay, or without a capRetry storms that keep a struggling provider down7
7Responses applied in arrival order (search, autosave)Stale response overwrites a fresh one5, 6
8A cache of promises with no eviction on rejectionOne failure cached forever3, 7
9A callback that is sometimes called synchronouslyZalgo: ordering depends on cache state2
10throw inside a callback, or an await with no try at the boundaryProcess crash, or an unhandled rejection that exits Node 15+2, 3, 8
11Listeners, intervals or subscriptions with no removalLeaks, and handlers firing for unmounted components6
12Sync CPU work in a request path, or a loop that only awaits in-memory workBlocked event loop; every request waits1, 8
13Stream code that ignores write()'s return value or splits on chunk boundariesMemory growth; corrupted parsing9
14Work acknowledged before it is persisted (webhooks, queues)Lost events on crash8, 10
15Non-idempotent handlers for anything that can be delivered twiceDouble charge, double email5, 10

How Each Chapter Works

Every chapter from 1 to 11 has the same parts, so you know where to look:

PartWhat it isHow to use it
How to read this chapterA short frame and a vocabulary tableSkim it. Come back to the table when a term is unclear
Concept partsEach part is built on a mechanism (which queue, which await, which interleaving), not a rule to memoriseRead slowly. If a rule seems arbitrary, the mechanism is what to remember
Simulations (sim blocks)Step-through sims: the event loop, racing requests, concurrency pools, debounce and throttlePredict before you press step. A sim you click through without predicting teaches nothing
TimelinesTwo or three requests side by side, step by step, with a question that pauses the timeline until you guessAnswer the question out loud before revealing. This is the interview skill
Spot the bugA 10–40 line realistic snippet, then "What's wrong?" The answer is foldedGive yourself two minutes. Write the bug, the interleaving and the fix before opening the answer. Then compare the "say it" line with what you would have said
Build itA polyfill spec, a folded reference solution, and the edge cases interviewers checkWrite it yourself first, in a file, and run it. Then read the edge-case table
The Causal Chains"If X → Y → Z" lines that the reader turns into click-to-reveal self-testsCover the consequence and say it. These are your revision cards
Review checklist5–10 things to grep for when the topic shows up in a codebaseUse it on your own code. Then on the gym repos

The gym

The section has a practice repo, js-gym/, with small services that have planted bugs and tests that don't catch them. Treat each exercise as a take-home: set a timer, read the code cold, write down every bug with its interleaving, fix it, and add a test that would have caught it. Then check the answer key. Chapter 12 explains how to run it.

ExerciseThemePractises chapters
01-search-boxSearch-as-you-type5, 6
02-credit-walletCredit ledger with a fake async DB5
03-llm-clientMulti-provider LLM client6, 7
04-job-runnerIn-process job queue7, 8
05-webhook-handlerPayment webhooks5, 8, 10
06-cache-layerRead-through cache3, 5
07-event-busPub/sub2, 6
08-stream-parserLLM SSE stream9
09-legacy-callbacksCallback-style module2
10-polyfill-kataBuild, not fix: stubs plus tests2, 3, 7, 11
review-drills/30-minute reviews of larger files10

Suggested Study Order

If your interviews are soon, do the highest-yield chapters first. Race conditions and async/await traps are planted in almost every review exercise; concurrency control and failure handling are what turn a "found the bug" into a "senior" score.

  1. Chapter 5 — Race Conditions. The core skill. Do every timeline.
  2. Chapter 4 — async/await Traps. The most common planted bugs.
  3. Chapter 7 — Controlling Concurrency. Pools, rate limits, retries with backoff and jitter. Your LLM failover experience maps straight onto this.
  4. Chapter 8 — The Node Process Under Failure. Crashes, graceful shutdown, and real parallelism.
  5. The gym: 02-credit-wallet, 03-llm-client, 04-job-runner, then review-drills/.
  6. Chapter 11 — Polyfills: drill Promise.all, debounce, asyncPool and promisify until you can write them without looking.
  7. Chapter 10 — Reviewing a Codebase: how to run a 30-minute review and defend your designs.
  8. Then the rest in order: 1, 2, 3, 6, 9.

With more time, read 1 through 11 in order and do each gym exercise after the chapters it practises.


All Chapters

#ChapterAfter it, you can catch…
0Course map (this page)—
1The Event Loop Under PressureOrdering puzzles, microtask starvation, loops that never yield, long tasks, setImmediate vs setTimeout flakiness
2Callbacks Done WrongZalgo, callbacks called twice or never, throws that crash the process, lost this, broken promisify
3Promises in Real CodeFloating promises, the construction antipattern, swallowed errors, unhandled rejections, wrong combinator choice
4async/await TrapsMissing await, forEach(async), accidental serial work, return vs return await, errors outside try
5Race Conditions in Single-Threaded JSCheck-then-act across await, double charge, double submit, stale responses, lost updates
6Cancellation and CleanupMissing timeouts, unaborted losers, leaking listeners and intervals, React effects without cleanup
7Controlling ConcurrencyUnbounded Promise.all, pool bugs, fail-fast that keeps running, retry storms
8The Node Process Under FailureCrashes from unhandled errors, lost work on shutdown, blocked event loops, when to use worker_threads
9Streams and Async IteratorsIgnored backpressure, chunk-boundary bugs in SSE parsing, readers that leak on abort
10Reviewing a CodebaseHow to prioritise findings, write them up, and defend a design under questioning
11The Polyfill SetEvery polyfill round: bind, array and object methods, event emitters, promise combinators, debounce, throttle
12The GymPractice repos with planted bugs and answer keys

What's Next

Start with Chapter 1 — The Event Loop Under Pressure if you are going in order, or jump to Chapter 5 if your interview is this week.