BuildBot

Types in Motion

Narrowing & discriminated unions

Lesson 1 of 3

What you'll learn

  • Understand how TypeScript narrows a union as you test it
  • Model "one of several shapes" with a discriminated union
  • Use an exhaustive switch that the compiler checks for you

A union type says a value is one of several things: string | number. On its own that's not very useful — you can't call .toFixed() on something that might be a string. Narrowing is how you earn access to the specific type: as you test a value, TypeScript shrinks the union.

function format(value: string | number) {
  if (typeof value === "number") {
    return value.toFixed(2); // here, value is `number`
  }
  return value.trim(); // here, value is `string`
}

Discriminated unions

The most powerful pattern in everyday TypeScript is the discriminated union: several object shapes that share a literal "tag" field. The tag lets the compiler tell them apart.

type Result =
  | { status: "ok"; data: string }
  | { status: "error"; message: string };

When you switch on the tag, each branch narrows to exactly one shape — so result.data is only reachable when status === "ok".

Exhaustiveness checking

Assign the value to a never in the default branch. If you later add a new variant and forget to handle it, the code stops compiling — the compiler becomes your checklist.

The challenge models an API result. Run it, then try adding a third variant to the union and watch where you'd need to handle it.

Narrow a discriminated union

Run it. Each branch sees only the fields that exist for that status.

Loading editor…

Next: writing functions that work for any type without losing type safety.

Sign in to save your progress across devices.