TypeScript63 min total · 21 parts
TypeScript Fundamentals: Types, Interfaces, Generics, and Why It Catches Bugs Before Runtime
Part 21 of 21 · ~3 min
Common Mistakes Worth Remembering
Seven ways this specific codebase — or one shaped like it — actually breaks, mapped to the chapter that explains why:
- A field with a typo compiles and ships anyway. Passed through a named variable instead of a fresh object literal, which quietly turns off excess-property checking. (Structural typing.)
- Two calls that look identical behave completely differently, and only one throws. An overload's return type genuinely depends on which signature matched — check which one actually fired before assuming the bug is elsewhere. (Functions, overloads, and
this.) - A new status value falls through into the wrong branch, silently. A discriminated union grew a variant and the
switchhandling it wasn't updated —assertNeverwould have refused to compile the moment that happened. (Discriminated unions.) - A value that's obviously correct in the source still gets rejected by the compiler. A literal that widened to its general type the instant it became a property of a plain object.
as constis the fix. (Literal types.) err.messageworks in testing and then explodes on a real error in production. Thecatchvariable defaults toany; onlystrict(oruseUnknownInCatchVariablesspecifically) makes itunknownand forces a check first. (unknown,any, andnever.)- A cast makes an error message disappear instead of the actual bug.
as unknown as Xforces past the one check that would have caught the mismatch — exactly the spot a real mistake goes unreported rather than flagged. (Type assertions.) - A reviewer's inbox goes quiet with nothing in any log to explain it. An
Applicant | undefinedread without a check. This is the bug that opened this whole reference, andstrictNullChecksis the only thing on this list that actually prevents it rather than just catching it faster. (tsconfig.json.)
Here's the queue as it actually exists by the end of this reference — every mechanism above, compressed into about a dozen lines:
class ApplicationQueue {
private items: Applicant[] = [];
constructor(readonly queueId: string) {}
enqueue(applicant: Applicant): void { this.items.push(applicant); }
dequeue(): Applicant | undefined { return this.items.shift(); }
get size(): number { return this.items.length; }
}
function notifyReviewer(queue: ApplicationQueue, reviewer: Reviewer): void {
const next = queue.dequeue();
if (!next) return; // required, once strictNullChecks is on
sendEmail(reviewer.email, `Review ${next.name}`, `/applicants/${next.id}`);
}
The same bug, somewhere else entirely
Building everything around one running program is how the ideas actually stick — but it leaves open a real question: did the mechanism stick, or just this particular story? The way to check is to look for the same shape somewhere that shares nothing with the queue at all. Here's the exact bug that opened this reference, in a script with no applicants, no reviewers, and nothing to do with hiring — a shipping-rate lookup by zip code:
const rates = new Map<string, number>();
rates.set("94103", 4.99);
function getRate(zip: string): number {
const rate = rates.get(zip); // Map.get() always returns V | undefined, for every key, every time
return rate * 1.08; // Error under strictNullChecks: 'rate' is possibly 'undefined'
}
Map.get() hands back V | undefined for the identical reason dequeue() does: there's no value to return for a key — or a queue — that doesn't have one. No applicant record anywhere near this function, and the bug is unmistakably the same one. If that's visible on sight, the lesson survived past the specific story it was taught through, which was the entire reason to tell only one story in the first place.
None of this exists in isolation from the rest of the stack. React Fundamentals is the component-and-hooks side of what a typed frontend actually looks like day to day, and JavaScript Core Concepts is the runtime underneath all of it — scope, closures, this, the event loop — none of which TypeScript changes even slightly; it only ever describes it more precisely. Reading about narrowing, generics, and the erasure gotchas gets you partway there. Actually watching one of these compile errors fire, on code you wrote, is what makes it stick — the code lab has exercises built for exactly that.
Continue learning
- Interview & Career PrepThe Non-Technical Half of the Interview: Behavioral Questions, the STAR Method, and What Recruiters Are Actually Scoring
- AI & LLM EngineeringAI & LLM Engineering Fundamentals: Prompting, RAG, Embeddings, and Function Calling
- TestingTesting Fundamentals: Unit, Integration, and E2E Tests Done Right