Reading TypeScript like a reviewer
On a real team, almost nothing reaches production without another developer reading it first. This final best-practices lesson turns everything the module taught into a lens you can read code through — as the person being reviewed, and soon as a reviewer. It closes the module the way the whole course closes: write code for the human who reads it next.
Why review, and the mindset
Code review is not about typos — the compiler and linter catch those. It exists to catch bugs a test would miss, spread knowledge (now two people understand the code), keep the codebase coherent, and teach in both directions. The mindset that matters most: review the code, not the coder. "This can be null here" is useful; "you always forget null" is not. As the author, receive feedback the same way — it is about making the code better, not a verdict on you. Teams where review is a shared craft, not a gauntlet, ship better software and are better to work in.
The TypeScript reviewer's checklist
When you read a change — your own before submitting, or a teammate's — work down these, roughly in order of importance. Every item is a lesson from this course.
1. Type honesty — is the code lying to the compiler?
- Any
anythat should beunknownor a real type? (prefer-unknown lesson.) - Any
asor!that should be a narrowing check or validation? (avoid-assertions lesson.) - Is external data validated (
unknown+ a guard/Zod), or just asserted to have a shape? (The most common real bug.)
2. Modelling — do the types tell the truth about the data?
- Do the types allow illegal states? A
booleanwhere a union fits, astringwhere a literal union fits, optionals that should be a discriminated union? (model-with-types lesson.) - Are failures modelled honestly — a
Resultfor expected failures,throwfor exceptional ones? (errors-typed lesson.) - Is a discriminated union handled exhaustively (the
nevercheck), so a new case cannot be silently dropped? (unions module.)
3. Correctness — does it actually work?
- Nulls and
undefinedhandled (not!'d away)? Promises awaited (no "used a promise as a value")? Independent async work concurrent (Promise.all), not sequential?- Off-by-ones, empty collections, the right data structure (a
Setfor membership, not an array scan)?
4. Readability — can you understand it without asking?
- Do names reveal intent? Are functions small and single-purpose?
- Is it idiomatic TypeScript, or over-clever — a baroque conditional type where a simple one (or a runtime check) would do? (building-utility-types lesson: mastery is restraint.)
5. Tests — is it proven?
- Is there a test for the new behaviour, including edge and failure cases? Would it actually fail if the code were wrong?
Reading a change: a worked eye
Here is a snippet as it might arrive in review. Read it as a reviewer before the notes:
async function loadUser(id: any) {
const res = await fetch("/api/users/" + id);
const user = await res.json() as User;
return user.name!.toUpperCase();
}
A careful reviewer flags several things, each a lesson from this module:
id: any— should bestring(ornumber);anyhere is a lie that lets any argument through. (prefer-unknown.)res.json() as User— asserting the shape without checking it; if the API sends something else, this crashes. Should beunknown+ a guard (or Zod). (avoid-assertions, prefer-unknown.)user.name!— a non-null assertion that will crash ifnameis missing; the data was never validated, so this is a real risk. (avoid-assertions.)- No error handling —
fetchfailing or a non-OK response are unhandled; and the return type is unstated. (errors-typed.)
The improved version:
async function loadUser(id: string): Promise<Result<string>> {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) return { ok: false, error: `HTTP ${res.status}` };
const data: unknown = await res.json();
if (!isUser(data)) return { ok: false, error: "Invalid user data" };
return { ok: true, value: data.name.toUpperCase() };
}
Typed parameter, validated data, a Result return that makes failure visible and handled, and not a
single lie to the compiler. A good review turns the first version into the second — not by rewriting it
for the author, but by pointing at each issue so the author learns to see it.
Giving and receiving feedback well
The human half matters as much as the technical:
- Be specific and kind. "Consider
unknown+ a guard here —as Userwould crash on bad data" is actionable. "This is unsafe" is not. - Distinguish must-fix from nice-to-have. A missing validation is a blocker; a naming preference is a suggestion. Say which is which.
- Ask, do not command, on judgement calls. "Would a discriminated union be cleaner here?" invites thought; sometimes the author has a reason.
- As the author, do not defend — understand. If a reviewer misread the code, that is a signal the code was unclear; explain, then usually make it clearer anyway.
- Praise good types too. Noting an elegant discriminated union or a well-placed
Resultteaches and encourages.
The whole course, as one habit
Step back and see what this module — and this course — was really for. Null safety, unions, generics, the
advanced type system, best practices: they are not separate topics. They are one habit — model reality
honestly in types, do not lie to the compiler, and write for the human who reads next. The compiler does
not care about your names or your modelling; the program runs the same. All of it is for the reader —
your teammate, your reviewer, your future self — and for making bugs impossible rather than merely caught.
A developer who internalises that — who reaches for unknown over any, models illegal states out, and
keeps types as simple as the problem allows — is the one worth hiring and the one a team wants to keep.
That is the difference this course has been building toward, and the capstone is where you put it into
practice.
Check your work
What code review is for. Catching bugs tests miss, spreading knowledge, keeping the codebase coherent, and teaching — not typos.
The core mindset. Review the code, not the coder; feedback is about the change, never the person.
The five review dimensions. Type honesty, modelling, correctness, readability, tests.
Type-honesty things to check. any that should be unknown, as/! that should be narrowing,
external data asserted instead of validated.
Modelling things to check. Types allowing illegal states, failures modelled with the right tool
(Result vs throw), exhaustive union handling.
What makes a test worth having. It would actually fail if the code were wrong.
How to give feedback well. Specific and kind, must-fix versus nice-to-have, ask on judgement calls, praise good work.
The one habit behind the whole course. Model reality honestly in types, do not lie to the compiler, and write for the human who reads next.
Practice
- Review the buggy
loadUseryourself before reading the notes. List every issue, then compare. - Rewrite it into the safe version and confirm it validates data and returns a
Result. - Review a piece of your own TypeScript against the five-dimension checklist. Find at least three improvements.
- Rewrite a "this is unsafe" comment into specific, kind, actionable feedback.
- Find an
any, anas, or a!in code you can access and write the review note that would flag it. - Find a type that allows illegal states in code you know and write the note suggesting a discriminated union.
- Write down, in your own words, the one-sentence habit this course comes down to, and pin it where you code.
Official documentation
- typescript-eslint — recommended rules — The rules a review often checks against automatically.
- TypeScript — Do's and Don'ts — Common mistakes to flag.
- Google — How to do a code review — A language-agnostic reviewer's guide.
Next module — Design Patterns in TypeScript: which patterns the type system dissolves, and which earn their place.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship