Basic

Unions and Type Guards

Combine types with unions and narrow them at runtime with type guards.

What you will see

HI boom

About 2 min of reading

Try It

A union `string | number` lets a value be any member. Inside a function, TypeScript only exposes operations valid for every member until a `typeof` or `instanceof` guard narrows it.

1

Union types

function pad(value: string | number): string { return String(value).padStart(3, "0"); } console.log(pad(5), pad("hi"));

Before you run it, say out loud what it will print. Then open the answer.

What it prints
005 0hi
2

typeof guards

function format(id: string | number): string { if (typeof id === "number") { return `#${id}`; } return `@${id}`; } console.log(format(42), format("Ada"));

Before you run it, say out loud what it will print. Then open the answer.

What it prints
#42 @Ada
3

instanceof guards

function describe(value: string | Error): string { if (value instanceof Error) { return value.message; } return value.toUpperCase(); } console.log(describe("hi"), describe(new Error("boom")));

Before you run it, say out loud what it will print. Then open the answer.

What it prints
HI boom

Put it in order

Before you write anything, put these lines back in the right order. Click a line to move it up one step. This support step is easier than writing code from scratch, and it is here to make the last example stick.

Data Flow

A value holds one of several possible types; a guard inspects it and narrows the union for safe use.
1 Value typed as union -> could be any member
↓
2 Guard runs at runtime -> inspects actual type
↓
3 Inside guard -> compiler narrows to one member
↓
4 Narrowed value -> member-specific members are usable
Why this works
Why narrowing matters

A union exposes only the members shared by all its types. To use a member-specific property you must narrow first:

function len(v: string | number): number {
  if (typeof v === "string") {
    return v.length;   // allowed after narrowing
  }
  return v.toFixed().length; // number path
}

Without the typeof guard, v.length would be a compile error because numbers have no length.

Custom type guards

You can write your own guards with a predicate return type:

function isString(v: unknown): v is string {
  return typeof v === "string";
}
function onlyStrings(items: unknown[]) {
  return items.filter(isString);  // result: string[]
}

The v is string return type tells the compiler to treat the value as a string in the true branch, which is powerful for filtering and validation.

Examples

Union types

Multiple possible types

function pad(value: string | number): string {
  return String(value).padStart(3, "0");
}
pad(5);    // "005"
pad("hi"); // "0hi"

A union string | number means the value may be either. Inside the function, TypeScript only lets you use the operations common to both until you narrow it.

typeof guards

Narrowing with typeof

function format(id: string | number): string {
  if (typeof id === "number") {
    return `#${id}`;   // id is narrowed to number here
  }
  return `@${id}`;     // id is narrowed to string here
}

typeof is a type guard: inside the if, the compiler narrows the union to the matching member.

instanceof guards

Narrowing objects

function describe(value: string | Error): string {
  if (value instanceof Error) {
    return value.message;   // narrowed to Error
  }
  return value.toUpperCase(); // narrowed to string
}

instanceof narries class instances. It is the standard guard for distinguishing Error from other values.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Interfaces and Object Types
Previous Lesson
Interfaces and Object Types
Next Lesson
Generics
Generics →