Skip to content

Type Guards & Narrowing#

typeof Type Guard#

function isNumber(x: any): x is number {
  return typeof x === "number";
}

function process(val: string | number) {
  if (typeof val === "string") {
    console.log(val.toUpperCase());
  } else {
    console.log(val.toFixed(2));
  }
}

instanceof Type Guard#

class Dog { bark() {} }
class Cat { meow() {} }

function animalSound(animal: Dog | Cat) {
  if (animal instanceof Dog) {
    animal.bark();
  } else {
    animal.meow();
  }
}

in Operator Narrowing#

interface Bird { fly: () => void; }
interface Fish { swim: () => void; }

function move(animal: Bird | Fish) {
  if ("fly" in animal) {
    animal.fly();
  } else {
    animal.swim();
  }
}

Equality Narrowing#

function print(val: string | null) {
  if (val === null) return;
  console.log(val.toUpperCase()); // val is string
}

Truthiness Narrowing#

function printName(name?: string) {
  if (name) {
    console.log("Hello " + name);
  }
}

Custom Type Predicate#

interface User { name: string; }

function isUser(obj: any): obj is User {
  return obj && typeof obj.name === "string";
}

if (isUser(someData)) {
  console.log(someData.name);
}

Discriminated Unions#

type Shape = 
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number };

function getArea(s: Shape) {
  if (s.kind === "circle") {
    return Math.PI * s.radius ** 2;
  } else {
    return s.side ** 2;
  }
}

Control Flow Analysis#

function example(x: string | number) {
  if (typeof x === "string") return;
  // x is number here
  console.log(x.toFixed());
}

Assertion Functions#

function assertIsString(val: any): asserts val is string {
  if (typeof val !== "string") throw new Error("Not a string");
}

const val: any = "hello";
assertIsString(val);
console.log(val.toUpperCase());

Nullish Coalescing (??)#

const input = null;
const value = input ?? "default"; // "default"

Optional Chaining (?.)#

const user = { details: { age: 30 } };
const age = user?.details?.age;