TypeScript63 min total · 21 parts
TypeScript Fundamentals: Types, Interfaces, Generics, and Why It Catches Bugs Before Runtime
Part 1 of 21 · ~2 min
Overview
Every team that adopts TypeScript seriously has a story about the bug that finally made them take it seriously. Here's CodeOath's.
The internal reviewer queue — the screen a hiring manager opens to pull the next take-home submission, read it, score it, and move that applicant forward or reject them — used to be a few hundred lines of ordinary JavaScript. Pulling the next applicant off the queue and emailing the assigned reviewer looked like this:
function getNextApplicant(queue) {
return queue.shift();
}
function notifyReviewer(applicant, reviewer) {
sendEmail(reviewer.email, `Review ${applicant.name}`, `/applicants/${applicant.id}`);
}
const next = getNextApplicant(reviewQueue);
notifyReviewer(next, currentReviewer);
For months, this was completely unremarkable code. Then one Friday afternoon the queue emptied out mid-batch — every applicant for the week had already been claimed — and a reviewer refreshed the page one more time anyway. queue.shift() on an empty array hands back undefined, which is exactly what getNextApplicant returned. notifyReviewer never asked whether next was actually an applicant before reading next.name off it, so it threw two lines later, mid-email. Nobody happened to be watching that particular log on a Friday afternoon, so review notifications for the rest of the day simply stopped, silently, and stayed stopped until someone noticed on Monday that nobody had been reviewed since lunch on Friday.
Read the code again and try to spot the bug without the story attached. It's genuinely hard — notifyReviewer looks exactly like every other function around it, and nothing on the page would have told a reviewer anything was wrong. That gap, between "looks fine" and "is fine," is what TypeScript is actually for. It won't make getNextApplicant return something other than undefined when the queue is empty — arrays still behave exactly the way they always have. What it will do, once it's actually configured to mean it, is refuse to compile notifyReviewer at all until the empty case gets handled — because the moment you tell it honestly that a value might be missing, it holds you to checking for that. We're going to rebuild this queue in TypeScript from the ground up, one mechanism at a time, and we'll land back on this exact function once the tools to fix it properly are in place. Read straight through for the full argument, or use the sidebar to jump to whatever piece you're actually stuck on.