Skip to content

The review checklist

The index lesson gave the reading order; the AI bug catalog gave 24 concrete patterns. This page turns both into one checklist you can actually run against a diff, grouped into the same six areas the rest of this course uses: event loop & async, streams & resources, HTTP boundaries, modules & packages, errors & process, and testing & tooling. Every item links to the lesson that explains the “why” in full.

  • Is every Promise-returning call either awaited or given an explicit .catch()? See Async/Await.
  • Does anything pass an async callback to Array.prototype.forEach, .map() used for side effects, or a similar method that never awaits its callback? See Async/Await.
  • Does a Promise.all(...) run over a list whose length is not bounded by the code itself (user input, a full table scan)? See Promises.
  • Is setTimeout(fn, 0) used anywhere setImmediate or process.nextTick more accurately describes when the work should actually run? See libuv and the Threadpool.
  • Does any synchronous work — a busy loop, readFileSync, a large JSON.parse — run inside a path that also needs to serve other requests concurrently? See Call Stack.
  • Does every EventEmitter-based object (a server, a socket, a custom class) have an 'error' listener registered? See EventEmitter.
  • Are listeners registered once and removed when their resource is torn down, rather than added fresh on every request? See EventEmitter.
  • Does every .pipe() call have an explicit error-handling plan, or would stream.pipeline() be the safer choice? See Pipe & Pipeline.
  • Are incoming chunks decoded independently with .toString(), instead of through a StringDecoder or full accumulation first? See Buffer.
  • Is Buffer.concat([...]) called once per incoming chunk inside a loop, instead of once at the end over a collected array? See Buffer.
  • Is every res.end() call guarded (if (res.writableEnded) return; or equivalent) so it can never fire twice for one response? See HTTP Server.
  • Are all headers set before the first res.write()/res.end(), never after? See HTTP Server.
  • Is the request body size-limited before it is fully buffered? See JSON API.
  • Is every JSON.parse on untrusted input wrapped in a try/catch that returns a clean 400? See JSON API.
  • Does every fetch call check response.ok before trusting the body, and pass an explicit timeout (AbortSignal.timeout(...))? See Requests.
  • Does the file mix require()/import, or reference __dirname/__filename inside a .mjs/ESM file? See Modules.
  • Is there a committed lockfile, and does the Dockerfile run npm ci rather than npm install? See Dependencies.
  • In package.json’s exports map, does a specific condition like types come before generic ones like default? See package.json.
  • Does the code call process.exit() anywhere it could instead return normally or set process.exitCode? See process.env.
  • Is console.log carrying information a log pipeline needs to parse, instead of one structured JSON object per line? See Debugging.
  • Does any child_process.exec, eval, or new Function call run on a string assembled from untrusted input? See Security Model.
  • Is a value from process.env compared or used arithmetically without first converting it from a string? See process.env.
  • Does every test() block contain at least one assertion that is actually reached? See Built-in Test Runner.
  • Is every mock.method/mock.fn applied at the same call site the code under test actually uses, not a reference captured earlier? See Built-in Test Runner.
  • Do any tests depend on real timers finishing instead of mock.timers with an explicit tick()? See Built-in Test Runner.

None of the checklist above requires reading every line first — most of it can be narrowed down with one grep over a diff or a whole source tree, using the exact strings this course’s catalog keys on:

Terminal window
grep -rnE "forEach\(async|readFileSync|\.pipe\(|process\.exit\(|catch *\{\}|shell: *true|exec\(|eval\(|JSON\.parse\(|res\.end\(" src/
Node.js
Output (verified on Node 24)
forEach\(async: true
readFileSync: true
\.pipe\(: true
process\.exit\(: true

Every one of the four patterns in this small sample matches — the same regular expressions the one-liner above uses, just run as JavaScript instead of shell grep for a runnable demo. A real grep hit is not proof of a bug (a .pipe() call might already have careful error handling elsewhere), only a place to slow down and check against the matching checklist item.

Paste this into a PR description or a review comment as a starting checklist — six lines, no links, meant to be checked off by hand:

[ ] Every promise awaited or .catch()-ed; no forEach(async ...); Promise.all has a bound
[ ] Every EventEmitter/stream has an 'error' listener; listeners removed, not accumulated
[ ] Every res.end() guarded against firing twice; body size limited; JSON.parse try/catch'd; fetch checks .ok + timeout
[ ] No require/import mixing, no __dirname in ESM; lockfile committed; exports specific-before-generic
[ ] No process.exit() mid-flight; structured logs, not console.log; no exec()/eval() on untrusted strings
[ ] Every test() has a reached assertion; mocks applied at the real call site; no real-timer tests

A checklist is a compressed, reusable form of the reading habits from the index lesson — each item exists because a real pattern in the AI bug catalog has bitten someone before. It speeds up a review; it does not replace reading the code the checklist cannot yet anticipate.

ApproachBenefitCost
The full six-group checklistTraces every item back to a lesson with the full explanationLong enough that skimming it under time pressure defeats the point
The six-line short versionFast enough to actually paste into every PRCompresses away the “why,” so a reviewer who disagrees with an item has to go look it up anyway
The one-line grepFinds the fastest, most literal hits in seconds, even across a huge diffOnly catches patterns with a stable, greppable string — most of the catalog’s “none — only a human” patterns have no such string
  • “Passing the grep means the diff is clean.” The grep pass finds only the patterns that have a stable string to search for — roughly a third of the AI bug catalog’s 24 patterns. The rest need an actual read.
  • “A checklist this long is slower than just reading the code carefully.” For a reviewer who has internalized the index lesson’s four habits, the checklist is a fast cross-check afterward, not a replacement for reading — used well, it takes seconds per item because the reviewer already knows what to look for.
  • “If an item isn’t on this checklist, it isn’t worth checking.” This checklist mirrors one catalog of 24 known patterns; a codebase’s specific domain (payments, auth, PII) always has boundary and suppression risks a generic checklist cannot enumerate in advance.

Run the grep pass first, then work through the six groups in order for anything grep did not already flag — the same triage a reviewer uses on the AI bug catalog itself, just condensed into checkboxes instead of full explanations.

🐛 Spot the bug

// Reviewer's note: "grep found nothing, so this file is clean."
async function chargeCustomer(order) {
const total = order.items.reduce(function (sum, item) { return sum + item.price; }, 0);
return billingClient.charge(order.customerId, total); // no timeout, no .ok-equivalent check, no retry limit
}
chargeCustomer(currentOrder); // BUG: floating promise -- not forEach-shaped, so the grep list never catches it
Show the answer

None of the ten grep strings this lesson lists appear anywhere in this file — no forEach(async, no exec(, no catch {}. But chargeCustomer(currentOrder); at the bottom is a completely ordinary floating promise: if billingClient.charge ever rejects (a declined card, a network error), nothing catches it, and the process crashes on an unhandled rejection. The checklist’s event-loop-and-async group item — “is every Promise-returning call either awaited or given an explicit .catch()?” — still catches this on a real read, which is exactly why the grep pass is a first pass, never the whole review.

The one-line grep in this lesson would find readFileSync and process.exit( in a diff. Which of the AI bug catalog's 24 patterns would it never find on its own, no matter how carefully the regex is tuned?
Why does this lesson provide both a full six-group checklist and a six-line short version?
Which checklist item from the "Event loop & async" group catches this file, even though it contains none of the one-line grep's ten strings?
async function chargeCustomer(order) {
  return billingClient.charge(order.customerId, order.total);
}
chargeCustomer(order);
Why is "the grep pass found nothing" not the same claim as "this diff has no bugs from the catalog"?
A reviewer checks off every item in the six-line short version for a diff in under a minute, without reading the diff itself first. What does a clean checklist result actually prove?