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.
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:
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");
}ASHAWhen 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#
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" }));swims fliesFiltering arrays#
filter with a type guard narrows the resulting array's type:
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()));[ '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[]:
const values: (number | undefined)[] = [3, undefined, 7];
const numbers = values.filter((v) => v !== undefined); // number[]
console.log(numbers.reduce((a, b) => a + b, 0));10Assertion 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:
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);
}asha@example.com
8081
name must be a string, got numberAssertion 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:
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));[ { 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
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);
}[ 'a@b.com', 'x@y.in' ]
-5 is not positiveKey takeaway#
Finished this lesson?