Custom Type Guards
Learn how to write your own type predicate functions to teach TypeScript custom narrowing logic.
Reading Time
16 min
Lesson
Lesson 25 of 30
When Built-In Narrowing Isn’t Enough
typeof, instanceof, and in cover a lot of narrowing scenarios, but sometimes checking a type requires custom logic — validating shape of unknown JSON data, for example. Custom type guards let you write that logic once and reuse it everywhere.
The is Type Predicate
A function whose return type is written as parameterName is Type tells TypeScript: "if this function returns true, treat the parameter as this specific type from now on."
A Basic Type Guard
interface Cat {
meow(): void;
}
interface Dog {
bark(): void;
}
function isCat(animal: Cat | Dog): animal is Cat {
return (animal as Cat).meow !== undefined;
}
function makeSound(animal: Cat | Dog) {
if (isCat(animal)) {
animal.meow(); // narrowed to Cat
} else {
animal.bark(); // narrowed to Dog
}
}Validating Unknown Data
Type guards are especially useful for safely validating data of type unknown — like a parsed API response — before trusting its shape.
A Type Guard for Unknown Data
interface User {
id: number;
name: string;
}
function isUser(value: unknown): value is User {
return (
typeof value === "object" &&
value !== null &&
"id" in value &&
"name" in value &&
typeof (value as User).id === "number" &&
typeof (value as User).name === "string"
);
}
function processData(data: unknown) {
if (isUser(data)) {
console.log(data.name); // safely narrowed to User
}
}Type Guards as Array Filters
A type guard used inside Array.prototype.filter() also narrows the resulting array’s element type — a common pattern for removing null or undefined values from an array.
Filtering Out null with a Type Guard
function isDefined<T>(value: T | null | undefined): value is T {
return value !== null && value !== undefined;
}
const values: (string | null)[] = ["a", null, "b", null];
const cleaned = values.filter(isDefined); // typed as string[], not (string | null)[]A Type Guard is a Promise, Not a Guarantee
TypeScript trusts the return type you declare, but doesn’t verify your guard’s logic is actually correct — an incorrect implementation compiles fine but narrows to the wrong type, reintroducing the exact bugs type guards are meant to prevent.
Best Practice
Write a type guard whenever you find yourself repeating the same manual narrowing check (typeof, in, property checks) in more than one place — extracting it keeps validation logic in a single, testable location.