Skip to content
Type Guards and Predicates
step 1/5

Reading — step 1 of 5

Learn

~2 min readBranded Types and Guards

A type guard is a runtime check that narrows the type. Built-ins like typeof, instanceof, in work for primitives and classes.

For your own types, define a type predicate:

function isString(x: unknown): x is string {
    return typeof x === 'string';
}

function process(input: unknown) {
    if (isString(input)) {
        console.log(input.toUpperCase());     // narrowed to string
    }
}

The return type x is string tells TypeScript: "if this returns true, narrow x to string".

For discriminated unions — built-in narrowing:

type Shape =
    | { kind: 'circle'; radius: number }
    | { kind: 'square'; side: number };

function area(s: Shape): number {
    if (s.kind === 'circle') {
        return Math.PI * s.radius ** 2;       // narrowed to circle variant
    }
    return s.side ** 2;                        // narrowed to square
}

Custom guards for complex shapes:

interface User {
    name: string;
    age: number;
}

function isUser(x: unknown): x is User {
    return typeof x === 'object'
        && x !== null
        && typeof (x as any).name === 'string'
        && typeof (x as any).age === 'number';
}

const data: unknown = JSON.parse(input);
if (isUser(data)) {
    console.log(data.name);                    // narrowed
}

Useful for parsing JSON safely.

asserts keyword — narrows AND throws if the assertion fails:

function assertIsUser(x: unknown): asserts x is User {
    if (!isUser(x)) {
        throw new TypeError('not a user');
    }
}

function handle(data: unknown) {
    assertIsUser(data);
    // Past here, data is User — TypeScript knows
    console.log(data.name);
}

in operator narrowing:

function describe(animal: { name: string } | { species: string }) {
    if ('name' in animal) {
        // narrowed to { name: string }
    } else {
        // narrowed to { species: string }
    }
}

Exhaustiveness pattern with never:

function area(s: Shape): number {
    switch (s.kind) {
        case 'circle': return Math.PI * s.radius ** 2;
        case 'square': return s.side ** 2;
        default:
            const _exhaustive: never = s;     // compile error if Shape grows
            return _exhaustive;
    }
}

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…