CodeOath
← All posts
TypeScript75 min total · 21 parts

TypeScript Fundamentals: Types, Interfaces, Generics, and Why It Catches Bugs Before Runtime

Contents — Part 6 of 21: Union Types and Narrowing
Part 6 of 21 · ~1 min

Union Types and Narrowing

function formatId(id: string | number): string {
  if (typeof id === "string") {
    return id.toUpperCase(); // TypeScript knows id is a string here
  }
  return id.toFixed(2); // and knows it's a number here
}

This is type narrowing — after the typeof check, TypeScript restricts what it believes id could be inside each branch, and only allows the methods valid for that narrowed type. Get the check wrong (or skip it) and the compiler stops you before a .toUpperCase() call ever hits a number at runtime.

Common narrowing techniques, beyond typeof:

// instanceof — narrowing by class
function handle(error: Error | string) {
  if (error instanceof Error) {
    console.log(error.message); // narrowed to Error
  } else {
    console.log(error.toUpperCase()); // narrowed to string
  }
}

// "in" — narrowing by property presence
interface Circle { kind: "circle"; radius: number; }
interface Square { kind: "square"; side: number; }
function area(shape: Circle | Square) {
  if ("radius" in shape) return Math.PI * shape.radius ** 2; // narrowed to Circle
  return shape.side ** 2; // narrowed to Square
}

// Truthiness narrowing
function printLength(s: string | null) {
  if (s) console.log(s.length); // narrowed to string — null is falsy, filtered out
}