Lesson 11 of 20

Type Guards

Built-in Type Guards

Type guards narrow a union type to a specific type within a code block. TypeScript understands typeof, instanceof, and in operators.

Example
// typeof guard
function process(value: string | number) {
  if (typeof value === "string") {
    return value.toUpperCase(); // string methods available
  }
  return value * 2; // number methods available
}

// instanceof guard
class Dog { bark() { return "Woof!"; } }
class Cat { meow() { return "Meow!"; } }

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

Custom Type Guards

You can create custom type guard functions using the 'is' keyword in the return type.

Example
interface Fish { swim(): void }
interface Bird { fly(): void }

// Custom type guard
function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

function move(animal: Fish | Bird) {
  if (isFish(animal)) {
    animal.swim(); // TypeScript knows it's Fish
  } else {
    animal.fly();  // TypeScript knows it's Bird
  }
}

// Truthiness narrowing
function printName(name: string | null) {
  if (name) {
    console.log(name.toUpperCase()); // string
  }
}

Exhaustive Checks with never

Use the never type to ensure all cases in a union are handled. If a new variant is added, TypeScript will flag unhandled cases.

Example
type Shape = "circle" | "square" | "triangle";

function getShape(shape: Shape): string {
  switch (shape) {
    case "circle":
      return "Round";
    case "square":
      return "Boxy";
    case "triangle":
      return "Pointy";
    default:
      // If we miss a case, this line would error
      const _exhaustive: never = shape;
      return _exhaustive;
  }
}