Skip to content
Laviyx Learn

Try

TypeScript course menu

TypeScript Type Guards, Type Predicates and Assertion Functions

Learn to write your own narrowing functions in TypeScript: type predicates (value is Type), validating unknown data such as API responses, filtering arrays with type guards, assertion functions (asserts value is Type), and inferred type predicates.

Advanced8 min readUpdated
On this page

Built-in checks like typeof and instanceof narrow types automatically (Narrowing). For checks that TypeScript cannot understand on its own, such as "is this object a valid User?", you write type guard functions.

Type predicates#

A type guard is a function that returns a boolean, with a special return type value is Type:

TypeScript
interface User {
  id: number;
  name: string;
}

function isUser(value: unknown): value is User {
  return (
    typeof value === "object" &&
    value !== null &&
    "id" in value &&
    typeof value.id === "number" &&
    "name" in value &&
    typeof value.name === "string"
  );
}

const data: unknown = JSON.parse('{"id": 1, "name": "Asha"}');

if (isUser(data)) {
  console.log(data.name.toUpperCase());   // data: User
} else {
  console.log("Not a user");
}
Output
ASHA

When isUser(data) returns true, TypeScript narrows data to User inside the if. In the else branch, it knows the value is not a User.

Guards for union members#

TypeScript
type Fish = { swim(): string };
type Bird = { fly(): string };

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

function move(pet: Fish | Bird): string {
  return isFish(pet) ? pet.swim() : pet.fly();
}

console.log(move({ swim: () => "swims" }), move({ fly: () => "flies" }));
Output
swims flies

Filtering arrays#

filter with a type guard narrows the resulting array's type:

TypeScript
const values: (string | null)[] = ["HTML", null, "CSS", null];

const isPresent = (v: string | null): v is string => v !== null;
const present = values.filter(isPresent);     // string[]

console.log(present.map((v) => v.toLowerCase()));
Output
[ 'html', 'css' ]

Since TypeScript 5.5, simple arrow functions like (v) => v !== null are recognised as type guards automatically (inferred type predicates), so values.filter((v) => v !== null) is also string[]:

TypeScript
const values: (number | undefined)[] = [3, undefined, 7];
const numbers = values.filter((v) => v !== undefined);   // number[]
console.log(numbers.reduce((a, b) => a + b, 0));
Output
10

Assertion functions#

An assertion function throws if a condition is false. Its return type asserts value is Type (or asserts condition) tells TypeScript that, after the call, the value has that type:

TypeScript
function assertIsString(value: unknown, name: string): asserts value is string {
  if (typeof value !== "string") {
    throw new TypeError(`${name} must be a string, got ${typeof value}`);
  }
}

function assert(condition: unknown, message: string): asserts condition {
  if (!condition) throw new Error(message);
}

const input: unknown = "  asha@example.com ";
assertIsString(input, "email");
console.log(input.trim());           // input: string from here on

const port: number | undefined = 8080;
assert(port !== undefined, "PORT is required");
console.log(port + 1);               // port: number

try {
  assertIsString(42, "name");
} catch (error) {
  console.log((error as Error).message);
}
Output
asha@example.com
8081
name must be a string, got number

Assertion functions suit validation at the start of a function or program: check once, then use the value freely.

Validating real data#

Type guards are the bridge between untyped runtime data and your types. For an API response:

TypeScript
interface Course {
  id: string;
  title: string;
  lessons: number;
}

function isCourse(value: unknown): value is Course {
  if (typeof value !== "object" || value === null) return false;
  const v = value as Record<string, unknown>;
  return typeof v.id === "string" && typeof v.title === "string" && typeof v.lessons === "number";
}

function parseCourses(json: string): Course[] {
  const data: unknown = JSON.parse(json);
  if (!Array.isArray(data)) throw new Error("Expected an array");
  return data.filter(isCourse);
}

const response = '[{"id":"ts","title":"TypeScript","lessons":39},{"id":"bad"}]';
console.log(parseCourses(response));
Output
[ { id: 'ts', title: 'TypeScript', lessons: 39 } ]

The invalid item is dropped instead of crashing the app later. For large or nested data, schema validation libraries such as Zod or Valibot generate both the check and the TypeScript type from one definition, which avoids writing guards by hand.

Practice#

Practice exercise

Write a type guard isEmail(value: unknown): value is string (a string containing "@" and a "." after it), and an assertion function assertPositive(n: number): asserts n that throws for zero or negative numbers. Use the guard to filter ["a@b.com", 42, "nope", "x@y.in"].

Show solution
TypeScript
function isEmail(value: unknown): value is string {
  if (typeof value !== "string") return false;
  const at = value.indexOf("@");
  return at > 0 && value.indexOf(".", at) > at + 1;
}

function assertPositive(n: number): asserts n {
  if (n <= 0) throw new RangeError(`${n} is not positive`);
}

const inputs: unknown[] = ["a@b.com", 42, "nope", "x@y.in"];
console.log(inputs.filter(isEmail));

try {
  assertPositive(-5);
} catch (error) {
  console.log((error as Error).message);
}
Output
[ 'a@b.com', 'x@y.in' ]
-5 is not positive

Key takeaway#

Finished this lesson?