User-defined Type Guards

Functions That Narrow: x is T

A function whose return type is value is T is a type guard. When it returns true, TypeScript narrows the argument to T in the calling code.

Example

type Product = { id: number; name: string; price: number };

function isProduct(x: unknown): x is Product {
  return (
    typeof x === "object" && x !== null &&
    "id" in x && typeof x.id === "number" &&
    "name" in x && typeof x.name === "string" &&
    "price" in x && typeof x.price === "number"
  );
}

const payload: unknown[] = JSON.parse('[{"id":1,"name":"Kettle","price":1299},{"id":"2","name":"Bad"}]');

for (const item of payload) {
  if (isProduct(item)) console.log("OK:", item.name, item.price);
  else console.log("Skipped invalid item:", JSON.stringify(item));
}

Output

OK: Kettle 1299
Skipped invalid item: {"id":"2","name":"Bad"}

Filtering with a Type Guard

Example

Passing a guard to filter narrows the resulting array type. Since TypeScript 5.5, simple arrow functions like x => x !== null are inferred as guards automatically.

type Coupon = { code: string; percent: number };

const coupons: (Coupon | null)[] = [{ code: "NEW50", percent: 50 }, null, { code: "SAVE10", percent: 10 }];

const valid = coupons.filter((c): c is Coupon => c !== null); // Coupon[]
console.log(valid.map((c) => c.code));

const inferred = coupons.filter((c) => c !== null);           // also Coupon[] in TS 5.5+
console.log(inferred.reduce((max, c) => Math.max(max, c.percent), 0));

Output

[ 'NEW50', 'SAVE10' ]
50