Type Narrowing and Runtime Behavior

Types exist only at compile time. They are erased before your code runs. This section covers what that means in practice and how to use type narrowing to work safely with broad types.

Types Are Erased at Runtime

All type information is removed during compilation. The compiled JavaScript knows nothing about your types, interfaces, or type aliases.

This means you cannot use TypeScript types for runtime checks:

interface User {
  name: string;
  age: number;
}

function isUser(value: unknown): boolean {
  return value instanceof User;
  // Error: 'User' only refers to a type,
  //        but is being used as a value here
}

Interfaces and type aliases do not exist at runtime. They produce no JavaScript output at all. If you need runtime type checking, you have to write actual JavaScript checks:

function isUser(value: unknown): value is User {
  if (typeof value !== "object" || value === null) {
    return false;
  }

  const candidate = value as { name?: unknown; age?: unknown };
  return (
    typeof candidate.name === "string" && typeof candidate.age === "number"
  );
}

The value is User return type is a type predicate. It tells TypeScript that if this function returns true, the value can be safely treated as a User.

Type Narrowing and Type Guards

Type narrowing is the process of refining a broad type to a more specific one using runtime checks. TypeScript understands several narrowing patterns:

typeof Guards

function double(value: string | number): string | number {
  if (typeof value === "string") {
    return value.repeat(2); // TypeScript knows: string
  }
  return value * 2; // TypeScript knows: number
}

double("ha"); // "haha"
double(21); // 42

The in Operator

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

function move(animal: Fish | Bird) {
  if ("swim" in animal) {
    animal.swim(); // TypeScript knows: Fish
  } else {
    animal.fly(); // TypeScript knows: Bird
  }
}

instanceof Guards

function formatDate(input: string | Date): string {
  if (input instanceof Date) {
    return input.toISOString(); // TypeScript knows: Date
  }
  return input; // TypeScript knows: string
}