careerbydoing
← Web development

Web development

The type with no values

About 6 min if you have seen this material before, 25 min or so if it is new to you.

Background
An answer in this project is either checked as text or as a number with a tolerance, so its type is a A TypeScript type built from several shapes distinguished by a shared literal field, which the compiler can narrow.: two shapes sharing a type field that holds a distinct literal. Checking that field narrows the type, which is why target.tolerance is reachable in one branch and not the other. There is a bonus that makes unions worth the trouble. If a switch handles every variant, then by the default branch the compiler has eliminated all of them, and what remains is the type inhabited by no values at all. Assigning that to a variable of the same type compiles today. But the day someone adds a third variant to the union, the elimination is no longer complete and the assignment fails. A forgotten case becomes a compile error rather than a bug found in production.
This is the real matcher from src/lib/grading.ts, where ExpectedAnswer is a union of exactly the exact and numeric shapes:
function matches(userAnswer: string, target: ExpectedAnswer): boolean {
  switch (target.type) {
    case "exact":   { /* ... */ }
    case "numeric": { /* ... */ }
    default: {
      const unhandled: ??? = target;
      throw new Error("Unhandled answer type");
    }
  }
}
What type does TypeScript infer for target in the default branch, the type that belongs where ??? is?

Past 1 hr on this? That usually means a missing prerequisite rather than a lack of effort — worth reading up before pushing further, starting with Narrowing and exhaustiveness checking (TypeScript handbook).

Want to go deeper?