Predicate Functions

Functions Whose Return Type Tells TS How to Narrow

Predicate Functions

A function with return type `x is T` is a user-defined type guard. Functions with `asserts` go further — they refine the type or throw.

3 min read Level 2/5 #typescript#predicate#asserts
What you'll learn
  • Author a predicate function
  • Use `asserts` for invariants
  • Recognize the use in `.filter`

A predicate function tells TS how to narrow a type. Two flavors — returning x is T (narrows after if) and asserts x is T (narrows for the rest of the scope).

x is T — Narrowing After a Check

function isString(x: unknown): x is string {
  return typeof x === "string";
}

function describe(x: unknown) {
  if (isString(x)) {
    x.toUpperCase();   // ✓ x is string
  }
}

Inside the if, TS treats x as string. Covered in the predicates lesson earlier.

asserts x is T — Narrowing for the Rest

function assertIsString(x: unknown): asserts x is string {
  if (typeof x !== "string") {
    throw new Error("Not a string");
  }
}

function describe(x: unknown) {
  assertIsString(x);
  x.toUpperCase();   // ✓ x is string for the rest of the function
}

Difference: after calling an asserts function, the narrowed type applies for the rest of the scope, not just inside an if.

The function must either:

  • Throw if the assertion fails
  • Or return normally (in which case the type is true)

A Pattern — Invariant Checks

function assertDefined<T>(value: T | null | undefined): asserts value is T {
  if (value == null) {
    throw new Error("Expected value to be defined");
  }
}

function process(user: User | null) {
  assertDefined(user);
  console.log(user.name);   // ✓ user is User
}

A nice replacement for if (!user) throw new Error(...) — single-line, type-aware.

Predicates in .filter

The most popular use:

const items = ["a", null, "b", null];

const filtered = items.filter((x): x is string => x !== null);
// filtered: string[]

Without the predicate, the type stays (string | null)[] — TS can’t prove the filter actually removed nulls.

The Trust Caveat

Predicates and asserts trust your implementation. TS doesn’t verify the runtime behavior matches the type-level claim. Treat them as part of your public API and test them.

End of Chapter

That wraps functions and inference. Next chapter — generics, the single biggest leap in TypeScript power.

Generics Intro →