← Back to TypeScript Course | Chapter 5: Type Aliases and Union Types | Lesson 6 of 9

Type Narrowing

Type narrowing is the process of reducing a broad type to a more specific type based on runtime checks. TypeScript understands many common checks and uses them to provide safer property and method access.

Narrowing with typeof

The typeof operator can distinguish between JavaScript's primitive types, including string, number, boolean, bigint, symbol, undefined, and function. TypeScript recognizes these checks specifically and uses them to narrow a union type down to just the matching branch.

Example: Narrowing with typeof

typescript
function describe(value: string | number) {
  if (typeof value === "string") {
    console.log(value.toUpperCase());
  } else {
    console.log(value.toFixed(2));
  }
}
describe(3.14159);

Narrowing with Equality Checks

Equality checks like === and !== can narrow both literal unions and values that might be null or undefined, by comparing directly against a specific value. TypeScript tracks exactly which possibilities remain after each comparison and adjusts the type accordingly.

Example: Narrowing with Equality Checks

typescript
function greet(name: string | null) {
  if (name !== null) {
    console.log(`Hello, ${name}`);
  } else {
    console.log("Hello, guest");
  }
}
greet(null);

Narrowing with Truthiness

TypeScript can also narrow a type based on how a value is used inside a plain conditional, since a truthiness check naturally rules out falsy possibilities like null, undefined, false, zero, and empty strings, depending on what the original type allowed. This works even without an explicit typeof or equality check.

Example: Narrowing with Truthiness

typescript
function printLength(value: string | null | undefined) {
  if (value) {
    console.log(value.length);
  } else {
    console.log("No value");
  }
}
printLength("hello");

Narrowing Object Unions

Object unions can be narrowed either by checking a property that only some members have, using the in operator, or through a dedicated discriminant property shared by all members. After narrowing, TypeScript permits access to properties that belong specifically to whichever member was selected.

Example: Narrowing Object Unions

typescript
type Cat = { meow: () => void };
type Dog = { bark: () => void };
function speak(pet: Cat | Dog) {
  if ("meow" in pet) {
    pet.meow();
  } else {
    pet.bark();
  }
}
speak({ bark: () => console.log("Woof") });

Narrowing with Custom Type Predicates

A custom type predicate is a function you write yourself that tells TypeScript exactly how to narrow a value, using a return type in the special form value is Type. This lets you package up more complex or reusable narrowing logic beyond what a single inline typeof or equality check can express.

Example: Narrowing with Custom Type Predicates

typescript
type Fish = { swim: () => void };
type 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();
  }
}
move({ swim: () => console.log("Swimming") });
🔒

Chapter Quiz — Complete all 9 topics to unlock

0/9 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.