CPCODELAB
TypeScript

Type Narrowing & Type Guards

11 min read

Type Narrowing & Type Guards

When a variable has a union type, TypeScript narrows it to a more specific type inside conditional branches. This is called type narrowing, and it happens automatically based on the checks you write.

typeof narrowing

ts
function format(value: string | number): string {
  if (typeof value === "string") {
    return value.toUpperCase(); // string here
  }
  return value.toFixed(2); // number here
}

in narrowing

ts
type Cat = { meow: () => void };
type Dog = { bark: () => void };

function speak(animal: Cat | Dog) {
  if ("meow" in animal) {
    animal.meow(); // Cat
  } else {
    animal.bark(); // Dog
  }
}

instanceof narrowing

ts
function handleError(error: unknown) {
  if (error instanceof Error) {
    console.error(error.message); // Error
  } else {
    console.error(String(error));
  }
}

Custom type guard functions

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

// Return type 'pet is Fish' is the type predicate
function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

function move(pet: Fish | Bird) {
  if (isFish(pet)) {
    pet.swim();
  } else {
    pet.fly();
  }
}

Narrowing also works with truthiness checks, equality (=== null), and the satisfies operator introduced in TypeScript 4.9. Always prefer structural narrowing over casting with as.

Ready to test yourself?
Practice TypeScript— quiz & coding exercises