TypeScript63 min total · 21 parts
TypeScript Fundamentals: Types, Interfaces, Generics, and Why It Catches Bugs Before Runtime
Part 2 of 21 · ~2 min
The Compiler and Type Erasure
Move formatDaysWaiting into a .ts file and give it types, and something worth noticing happens the moment you check what actually ships:
// queue-utils.ts
function formatDaysWaiting(days: number): string {
return `${days} day${days === 1 ? "" : "s"} in queue`;
}
// what actually reaches the browser or Node, after tsc runs
function formatDaysWaiting(days) {
return `${days} day${days === 1 ? "" : "s"} in queue`;
}
: number and : string are nowhere in the second version — not renamed, not compiled into some runtime check, just gone. tsc (or whatever's compiling your TypeScript under the hood — esbuild, SWC, Babel's preset) reads the annotated version, confirms every call in the codebase actually respects those types, and then produces plain JavaScript as though the annotations were never written. This is called type erasure, and the practical consequence trips people up constantly: a type is a fact the compiler knows and the running program does not. There's no typeof x === "Applicant" you can write, no way to log a type to the console, no way to branch on one at runtime — the only thing left to ask about a value once the program is actually executing is what it is in plain JavaScript terms, never what TypeScript once called it.
interface QueueEntry {
id: number;
enqueuedAt: number;
}
function logShape(entry: QueueEntry) {
console.log(typeof entry); // "object" — nothing here says QueueEntry, because nothing at runtime ever knew
}
This is also exactly what let the Friday-afternoon incident happen even in a codebase that was already partway converted to TypeScript. A type error, by itself, does not stop tsc from producing output — reporting a problem and refusing to emit anything are two separate behaviors, and TypeScript only does the first unless something downstream deliberately wires up the second. CodeOath's reviewer-queue service was in exactly that state for a while: getNextApplicant's return type honestly said Applicant | undefined, right there in the source, and the service still shipped with the bug intact, because nothing in the deploy pipeline ever ran tsc --noEmit as a required step — the bundler compiled the file, never checked whether the types actually lined up, and moved on. A type the build never asks about might as well be a comment. Making that check mandatory is the last piece of this whole reference, covered in the chapter on tsconfig.json — and that's also where this exact bug finally gets caught before it ships.