Settings
Your save file

Progress lives in this browser, on this device. Download the save file to move it to another device, or to keep a copy in case this browser clears its storage.

Season 2
Week 3 of 12

Reading time · 3 min

One comp · scored

Up next

Week 4 · The Quiet Week

Locked until you win

Week 3 · Early Weeks

Nominations

Union and literal types

'nominated' | 'safe' | 'evicted' beats loose strings. Sidebar: why unions outplay enums.

Somewhere out there is a production deploy with if (status === 'nominaded') sitting in a conditional, silently doing nothing, because nobody caught the typo and plain JavaScript will happily compare a string to a string all day long, right or wrong. It compiled. It shipped. It just never matched anything. This week's job is making that particular bug impossible to write.

Unions: type-level OR

You already know || at the value level: a || b picks whichever value is truthy. A union type is the same idea one level up, at the type level: string | null means "this value is a string, or it is null, nothing else." You reach for that one constantly already, probably without a name for it. Literal types push the idea further: instead of the general type string, a literal type like 'won' is the type whose only valid value is the exact string "won". Union a few literals together and you get a closed set of exact strings, which is a lot more specific than string.

The ES6 you write

let compResult = 'won'
 
function announceResult(result) {
  return result === 'won' ? 'HOH secured' : 'better luck next time'
}

The TS version

type CompResult = 'won' | 'lost' | 'forfeited'
 
let compResult: CompResult = 'won'
 
function announceResult(result: CompResult): string {
  return result === 'won' ? 'HOH secured' : 'better luck next time'
}

What changed

compResult goes from the general type string, which happily accepts any string including a typo, to the union 'won' | 'lost' | 'forfeited', which accepts only those three exact strings. Try to assign compResult = 'wonn' on the right and TypeScript rejects it before you ever run the code.

The never trick: proof you handled every case

A union of three literals means exactly three values are possible, no more. That gives TypeScript something plain JavaScript never had: it can tell whether a switch over that union covers every case. Here is the trick, and it is worth understanding rather than memorizing.

function describeResult(result: CompResult): string {
  switch (result) {
    case 'won':
      return 'HOH secured'
    case 'lost':
      return 'better luck next time'
    default: {
      const impossible: never = result
      return impossible
    }
  }
}

That switch is missing a case for 'forfeited'. Inside the default branch, TypeScript narrows result down to whatever is left after every handled case is subtracted, which here is still 'forfeited', one real, live value. Assigning a real value to a variable typed never, the type with no valid values at all, is a compile error. So the missing case shows up as a red squiggle on that assignment, at the exact spot the bug lives, not as a mystery bug report three weeks later. Add the 'forfeited' case and result inside default genuinely has nothing left to be, TypeScript narrows it all the way to never, and the assignment compiles clean. That is the whole trick: the compiler proves you handled every case by trying, and failing, to find a value that could still reach default.

That is the whole lesson. Now prove it in the comp below.

Up next

Week 4 · The Quiet Week

Type inference

Locked until you win