Type Narrowing

Narrowing is how TypeScript figures out, inside a specific branch of your code, that a value is actually one particular member of a union rather than any of the others — usually because of a check you already wrote for a completely ordinary reason.

typeof narrowing

TS typeof.ts
function printId(id: number | string) {
  if (typeof id === "string") {
    console.log(id.toUpperCase());
  } else {
    console.log(id.toFixed(2));
  }
}

printId("a9x");
printId(3.14159);
Output
A9X
3.14

Inside the if branch, TypeScript knows id can only be a string — the typeof check itself is what narrows the union down, so .toUpperCase() is allowed there without any extra casting. In the else branch, it's narrowed to number instead, so .toFixed() is allowed.

instanceof and property checks

TS instanceof.ts
class Cat {
  meow() { console.log("Meow!"); }
}
class Dog {
  bark() { console.log("Woof!"); }
}

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

speak(new Cat());
speak(new Dog());
Output
Meow!
Woof!

instanceof works the same way for classes — TypeScript narrows animal to Cat inside the if, so .meow() is available, and to Dog in the else, unlocking .bark().

A custom type guard

TS guard.ts
interface Fish { swim(): void; }
interface Bird { fly(): void; }

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();
  }
}

pet is Fish is a type predicate — it tells the compiler that whenever isFish returns true, the argument should be treated as a Fish from that point on. This lets you write your own narrowing logic beyond what typeof and instanceof can check on their own.

Note: a classic trap when narrowing is typeof null — it returns "object", not "null", a long-standing quirk baked into JavaScript itself. A check like typeof value === "object" will happily let null through, which usually isn't what you meant. Narrow out null explicitly first (if (value !== null && typeof value === "object")) whenever a union includes it.