TypeScript Type Predicates: Complete Guide to Custom Type Guards

Type predicates in TypeScript are powerful tools that enable you to create custom type guards, giving you precise control over type narrowing in your applications. Let’s dive deep into understanding and implementing type predicates effectively.

Type predicates are special return type annotations that tell TypeScript when a function can be used as a type guard. They follow the pattern parameterName is Type, allowing you to create custom type-checking functions that TypeScript can understand and use for type narrowing.

Table of Contents

Understanding Type Predicates

A type predicate is a special form of type annotation that follows this syntax:

function isString(value: any): value is string {
    return typeof value === 'string';
}
Code language: JavaScript (javascript)

In this example, value is string is the type predicate. When the function returns true, TypeScript knows that the value parameter is definitely a string.

Basic Type Predicate Examples

Let’s look at some practical examples of type predicates:

interface Bird {
    fly(): void;
    layEggs(): void;
}

interface Fish {
    swim(): void;
    layEggs(): void;
}

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

// Using the type guard
function moveAnimal(pet: Fish | Bird) {
    if (isFish(pet)) {
        // TypeScript knows pet is Fish here
        pet.swim();
    } else {
        // TypeScript knows pet is Bird here
        pet.fly();
    }
}
Code language: JavaScript (javascript)

Advanced Usage Patterns

Multiple Type Checks

You can create type predicates that check for multiple conditions:

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

function isValidUser(obj: any): obj is User {
    return (
        typeof obj === 'object' &&
        typeof obj.id === 'number' &&
        typeof obj.name === 'string' &&
        typeof obj.email === 'string'
    );
}

const processUser = (input: unknown) => {
    if (isValidUser(input)) {
        // TypeScript knows input is User here
        console.log(input.name);
    }
};
Code language: JavaScript (javascript)

Generic Type Predicates

Type predicates can also work with generics for more flexible type checking:

function isArrayOfType<T>(
    arr: unknown,
    validator: (item: unknown) => item is T
): arr is T[] {
    if (!Array.isArray(arr)) return false;
    return arr.every(item => validator(item));
}

function isNumber(value: unknown): value is number {
    return typeof value === 'number';
}

const data: unknown = [1, 2, 3, 4, 5];

if (isArrayOfType(data, isNumber)) {
    // TypeScript knows data is number[] here
    const sum = data.reduce((a, b) => a + b, 0);
    console.log(sum);
}
Code language: HTML, XML (xml)

Common Use Cases

Null and Undefined Checks

function isNonNullable<T>(value: T): value is NonNullable<T> {
    return value !== null && value !== undefined;
}

const values = ['hello', null, 'world', undefined];
const nonNullValues = values.filter(isNonNullable);
// TypeScript knows nonNullValues is string[]
Code language: JavaScript (javascript)

Custom Object Type Checks

interface ApiResponse<T> {
    data: T;
    status: number;
}

function isApiResponse<T>(value: unknown): value is ApiResponse<T> {
    return (
        typeof value === 'object' &&
        value !== null &&
        'data' in value &&
        'status' in value &&
        typeof (value as ApiResponse<T>).status === 'number'
    );
}
Code language: JavaScript (javascript)

Best Practices

  1. Keep Predicates Simple
    Focus each predicate on checking one specific type or condition.


  2. Use Descriptive Names
    Name your predicate functions clearly to indicate what they check for (e.g., isValidUser, isApiResponse).


  3. Combine with Type Guards
    Use type predicates alongside TypeScript’s built-in type guards for comprehensive type checking:


function isError(error: unknown): error is Error {
    return error instanceof Error;
}

function handlePotentialError(result: unknown) {
    if (isError(result)) {
        console.error(result.message); // TypeScript knows result is Error
    }
}
Code language: JavaScript (javascript)
  1. Avoid Type Assertions
    Use type predicates instead of type assertions when possible for better type safety:
// Good - Using type predicate
function isString(value: unknown): value is string {
    return typeof value === 'string';
}

// Avoid - Using type assertion
const value = someValue as string;
Code language: JavaScript (javascript)

Common Pitfalls

Incorrect Return Types

// ❌ Bad - Return type doesn't match predicate
function isNumber(value: unknown): value is number {
    return typeof value === 'string'; // Logic doesn't match return type
}

// ✅ Good - Return type matches predicate
function isNumber(value: unknown): value is number {
    return typeof value === 'number';
}
Code language: JavaScript (javascript)

Forgetting to Check All Properties

// ❌ Bad - Incomplete property checks
function isUser(value: unknown): value is User {
    return (
        typeof value === 'object' &&
        value !== null &&
        'name' in value
        // Missing checks for other required properties
    );
}

// ✅ Good - Complete property checks
function isUser(value: unknown): value is User {
    return (
        typeof value === 'object' &&
        value !== null &&
        'name' in value &&
        'id' in value &&
        'email' in value
    );
}
Code language: JavaScript (javascript)

Conclusion

Type predicates are a powerful feature in TypeScript that enables you to create custom type guards with precise type narrowing. By following the best practices and understanding common pitfalls, you can write more type-safe and maintainable code.

Remember to use type predicates when you need to:

  • Validate complex object structures
  • Handle unknown data from external sources
  • Create reusable type checking functions
  • Implement custom type guards for your domain-specific types

For more advanced TypeScript features, check out our guide on TypeScript Type Guards which complements this article with additional type-checking techniques.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Share via
Copy link
Powered by Social Snap