TypeScript Type Guards

Type guards are expressions that let TypeScript narrow a broad type down to a more specific one at particular points in your code, based on runtime checks like typeof, instanceof, in, and custom predicate functions.

typeof Guards for Primitives

Inside an if statement, checking typeof value === "string" (or "number", "boolean", etc.) narrows a union of primitives to just the matching branch for the rest of that block.

Narrowing a primitive union

function formatValue(value: string | number): string {
  if (typeof value === "string") {
    return value.trim();
  }
  return value.toFixed(2);
}

console.log(formatValue("  hi  ")); // "hi"
console.log(formatValue(3.14159));  // "3.14"

instanceof Guards for Class Instances

instanceof checks whether a value was constructed by a particular class, narrowing a union of class instances to the matching subclass inside the guarded branch.

Narrowing a class union

class ApiError extends Error {
  constructor(public statusCode: number, message: string) {
    super(message);
  }
}

function handle(error: Error | ApiError): void {
  if (error instanceof ApiError) {
    console.log(`API error ${error.statusCode}: ${error.message}`);
  } else {
    console.log(`Generic error: ${error.message}`);
  }
}

handle(new ApiError(404, "Not found"));
handle(new Error("Something broke"));

in Guards and Custom Type Predicates

The in operator checks whether a property exists on an object, which is useful for distinguishing plain object shapes that don't share a class hierarchy. For reusable or more complex checks, write a custom function whose return type is 'param is Type' — a type predicate.

Custom guard built on in

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

function isFish(pet: Fish | Bird): pet is Fish {
  return "swim" in pet;
}

function move(pet: Fish | Bird): void {
  if (isFish(pet)) {
    pet.swim(); // narrowed to Fish
  } else {
    pet.fly();  // narrowed to Bird
  }
}
  • typeof — narrows unions of primitive types like string, number, boolean
  • instanceof — narrows unions of class instances by constructor
  • in — narrows plain object unions by checking property presence
  • custom 'x is Type' predicates — reusable, composable guards for complex logic
  • discriminated unions with a literal 'kind' field — an alternative that plays well with switch statements
GuardCheck expressionTypical use
typeoftypeof x === "string"primitive unions
instanceofx instanceof MyClassclass instance unions
in"prop" in xplain object shape unions
custom predicateisX(x): x is Xreusable or multi-condition checks
Note: Pair custom type guards with discriminated unions and a switch on the literal 'kind' field, then add a default case that assigns the value to a variable typed never — the compiler will flag any unhandled case as an error.

Exercise: TypeScript Type Guards

Which operator narrows a union based on primitive types like 'string' or 'number'?