The review checklist
This is the checklist this module has been building toward: one page, six groups, every item phrased as a question you can actually answer by reading a diff — not a vague reminder to “be careful.” Each question links back to the lesson (in this module or an earlier one) that explains exactly why it matters and shows the compiling-but-wrong code it catches.
Boundaries & assertions
Section titled “Boundaries & assertions”- Every
as: does it assert a shape that is actually validated somewhere nearby, or is the assertion the only thing standing between this line and a crash? — Typing APIs & Boundaries - Every
any— explicit, or leaked from an untyped import’s inferred return type: where does it stop, and what crosses that boundary completely unchecked? — Typing APIs & Boundaries - Every
!: is the non-null claim backed by a nearby runtime check, or by hope? — Nullability,never& Errors - Every
@ts-ignoreor un-coded@ts-expect-error: what error is it hiding, and does removing it still reproduce that error today? — Why TypeScript - Every
catch (e): isenarrowed (instanceof Error) before.message/.stackis read? — Typing APIs & Boundaries - Every
Record<string, any>or bareobjectstanding in for real data: would a concrete interface have caught the typo or shape mismatch this code relies on? — Typing APIs & Boundaries
Nullability & defaults
Section titled “Nullability & defaults”- Every optional field checked with
if (x): does a valid0,"", orfalseneed to be treated differently from “the field is missing”? — Narrowing - Every
||used to supply a default: should it be??, so an explicit falsy value survives? — Nullability,never& Errors - Every
Readonly<T>/readonly T[]: does the surrounding code (wrongly) assume the protection reaches nested objects too? — Typing APIs & Boundaries - Every module-level object literal used as a shared “default”: is it ever mutated by a caller instead of copied? — Typing APIs & Boundaries
- Every call to an
asyncfunction or anything else returning aPromise: is itawaited,.catched, or explicitly discarded withvoid? — Performance & Pitfalls - Every
Promise.all(...): is the array passed inline (keeping each position’s type), or built in a variable first (which can silently widen to a union)? — Performance & Pitfalls - Every
switch/if-chain over a union: is there adefaultbranch that assigns the remainder to anever-typed variable? — Nullability,never& Errors - Every method read off an instance and passed elsewhere as a bare reference (
obj.method, no.bind, no arrow): does its body usethis? —this, Polymorphism & Binding
Generics & variance
Section titled “Generics & variance”- Every function parameter typed
T[]/Array<T>that only reads the array: could it bereadonly T[]instead, closing the covariance-plus-pushhole? — Generic Classes & Interfaces — Variance - Every hand-written type predicate (
x is T): does the function body actually check every field the predicate claims, or does it just returntrue? — Narrowing - Every generic
interface/classwith exactly one implementer and a type parameter used in exactly one position: does it earn its abstraction, or is it indirection with no reuse payoff? — Generic Functions & Constraints - Every
Omit/Pick/Excludeapplied to something that might be a union: has it actually been checked to distribute the way the author assumed? — The Utility Types - Every deeply recursive conditional type over user-controlled input: has it been checked against the compiler’s measured depth limit? — Recursive Types
Erasure & config
Section titled “Erasure & config”- Every
enum(numeric or string): does this codebase’s actual runner (node --experimental-strip-types,esbuild,swc) support it, or does it only compile undertsc? — Objects, Arrays & Tuples - Every parameter property (
constructor(public x: number)): the same question — does the runtime actually used to run this file support the emit it needs? — Build & Emit - Every
const enum: is it ever imported across a file or package boundary a single-file transpiler will process alone? — Build & Emit - Every plain
import { X }whereXis only ever used as a type: should it beimport type { X }? —tsconfigDeep Dive
Tooling
Section titled “Tooling”- Has
tsc --noEmit --strictactually been run on this diff — not just assumed clean because the editor shows no red squiggles? — Verification Tools - Has the typed
typescript-eslintrule set been run, not just the untyped defaults that ship in most starter configs? — Verification Tools - For a published package: does
@arethetypeswrong/clireport any resolution orexportsproblem? — Verification Tools - Does
knipreport any export this diff should have removed, or any dependency it no longer needs? — Verification Tools
The short version
Section titled “The short version”Pin this to the top of a PR template — it is the same checklist, compressed to one line per group:
[ ] Boundaries -- every as/any/!/@ts-ignore/catch(e): asserted, or actually checked?[ ] Nullability -- every falsy-check/||/Readonly/shared-default: does 0 survive, is it deep, is it copied?[ ] Async -- every promise call: awaited? Promise.all inline? every union switch: never-checked? every bare method ref: bound?[ ] Generics -- every T[] param: readonly enough? every predicate: really checked? every generic: earning its keep? every Omit/Pick: distributes? every recursion: under the limit?[ ] Erasure -- every enum/parameter property/const enum: does the actual runtime support it? every type-only import: import type?[ ] Tooling -- tsc --strict run? typed eslint run? attw/knip run on a package?And the single grep that surfaces most of the diff worth slowing down for:
grep -nE 'as |: any|!\.|!;|!\)|@ts-|JSON\.parse|\.json\(\)|enum |Object\.keys' <changed-files>Mental Model
Section titled “Mental Model”Six groups, one page:
1. Boundaries & assertions -- every place types and reality are allowed to diverge2. Nullability & defaults -- every place a falsy value or a shared object is easy to mishandle3. Async -- every place a Promise or a union can be silently mishandled4. Generics & variance -- every place abstraction either earns its keep or introduces unsoundness5. Erasure & config -- every place "compiles" and "runs" can diverge6. Tooling -- did anything besides your own reading actually check this?
Every question is answerable by reading the diff -- none require running the program first.Trade-offs
Section titled “Trade-offs”| What you get | Benefit | Cost |
|---|---|---|
| A fixed six-group checklist | Consistent coverage across every reviewer and every PR, not “whatever I remember to check today” | A checklist followed mechanically, without understanding why each item matters, becomes a box-ticking exercise |
| Questions instead of reminders | Each item has a definite yes/no answer readable from the diff | Some questions (variance, distribution, recursion depth) need the linked lesson’s background to answer correctly |
| A one-line-per-group short version | Fast enough to actually paste into a PR template and use every time | Compressed enough to lose the “why” — keep the full version linked nearby |
| A single grep one-liner | Catches most of the syntactic hot spots in seconds, before reading a single line of logic | A grep hit is not automatically a bug — every hit still needs the judgment the rest of this checklist trains |
Common Misconceptions
Section titled “Common Misconceptions”- “A checklist replaces judgment.” Every item here still requires reading the specific code around it — the checklist tells you where to look, not what you will find.
- “If the grep finds nothing, the diff is clean.” The grep list catches syntactic patterns; several patterns in this module’s catalog (a mutable shared default, a lying type predicate,
Omiton a union) leave no matching token at all. - “Six groups means six separate passes.” In practice, one read of the diff answers most of these questions at once — the six groups organize what to look for, not how many times to read.
- “This checklist is only for AI-generated code.” Every question here applies equally to a human’s PR; AI-generated code just produces violations at a higher volume and with more confident-looking comments.