thecodex.expert · The Codex Family of Knowledge
TypeScript

Type Guards

Built-in narrowing (typeof, instanceof) only goes so far — a custom type guard function lets you teach TypeScript your own, arbitrarily complex narrowing logic.

TypeScript 6.0 value is Type Last verified:
Canonical Definition

A type guard function's return type is written value is Type instead of a plain boolean — this special return type, called a type predicate, tells TypeScript that a true return narrows the checked value to Type everywhere the function is used as a condition. This extends narrowing beyond what typeof/instanceof/in can express to any arbitrarily complex runtime check.

Writing a custom type guard

The pet is Fish return type is what makes this special — a plain : boolean return would work at runtime identically, but wouldn't narrow anything for the type-checker.

TypeScriptcustom_guard.ts
type Fish = { swim: () => void };
type Bird = { fly: () => void };

function isFish(pet: Fish | Bird): pet is Fish {   // the type predicate
    return (pet as Fish).swim !== undefined;
}

function move(pet: Fish | Bird) {
    if (isFish(pet)) {
        pet.swim();   // narrowed to Fish, thanks to the type guard
    } else {
        pet.fly();     // narrowed to Bird
    }
}

Type guards with arrays: filtering out undefined

Without a type guard, .filter(x => x !== undefined) narrows the runtime values correctly but TypeScript's own inference still leaves the array type as (T | undefined)[] — a type guard fixes the type-level result to match reality.

TypeScriptfilter_guard.ts
const items: (string | undefined)[] = ["a", undefined, "b", undefined];

function isDefined<T>(value: T | undefined): value is T {
    return value !== undefined;
}

const filtered: string[] = items.filter(isDefined);
// without the type guard, TypeScript would still see (string | undefined)[]

Type guards for validating unknown data

This is one of the most practical real-world uses — safely narrowing genuinely unknown external data (a JSON API response, for instance) into a specific expected shape, with the check written once and reused everywhere that data is handled.

TypeScriptunknown_data_guard.ts
interface User { name: string; age: number; }

function isUser(data: unknown): data is User {
    return (
        typeof data === "object" && data !== null &&
        "name" in data && "age" in data
    );
}

const response: unknown = fetchApiResponse();
if (isUser(response)) {
    console.log(response.name);   // safely narrowed to User
}

Sources

1
Microsoft. TypeScript Handbook, "Using type predicates", typescriptlang.org/docs/handbook/2/narrowing.html.