GGistDev

Type Guards in TypeScript

Type guards refine types at runtime checks so the compiler knows which branch handles which type.

Built-in guards

function len(x: string | string[]) {
  if (typeof x === "string") return x.length; // string
  return x.length; // string[]
}

function handle(el: HTMLElement | null) {
  if (el instanceof HTMLDivElement) {
    el.align = "center"; // HTMLDivElement
  }
}

function hasId(x: unknown) {
  if (typeof x === "object" && x !== null && "id" in x) {
    // x: object with id
  }
}
  • typeof for primitives
  • instanceof for classes/DOM
  • in for property presence

User-defined type predicates

interface Ok<T> { ok: true; value: T }
interface Err { ok: false; error: string }

function isOk<T>(r: Ok<T> | Err): r is Ok<T> {
  return (r as any).ok === true;
}

const r: Ok<number> | Err = Math.random() > 0.5 ? { ok: true, value: 1 } : { ok: false, error: "x" };
if (isOk(r)) console.log(r.value); // r: Ok<number>

Discriminated unions

type Shape =
  | { kind: "circle"; r: number }
  | { kind: "rect"; w: number; h: number };

function area(s: Shape) {
  switch (s.kind) {
    case "circle": return Math.PI * s.r ** 2;
    case "rect": return s.w * s.h;
    default: {
      const _x: never = s; // exhaustive
      return _x;
    }
  }
}

Assertion functions

Signal to the compiler that a condition must hold.

function assertHas<K extends PropertyKey>(key: K, x: unknown): asserts x is Record<K, unknown> {
  if (!(typeof x === "object" && x !== null && key in x)) {
    throw new Error("missing key");
  }
}

Tips

  • Prefer user-defined predicates for reusable guards
  • Make unions discriminated with a literal kind tag
  • Use assertion functions for invariants that should stop execution when false