Type Narrowing in TypeScript: Tutorial & Examples

Updated: January 7, 2024 By: Guest Contributor Post a comment

Overview

Mark Twain once depicted the Mississippi as a relentless force, shaping the land as surely as the types undulate through the flow of TypeScript. This guide shepherds you down the river of type narrowing in TypeScript, a technique essential for mastering the currents of strong typing.

Understanding Type Narrowing

In the realm of TypeScript, type narrowing refers to the process of refining variable types from a broader scope to a more specific one. ‘Tis akin to distinguishing catfish from the brimming selection of the Mississippi’s offerings. Let us cast our net with a simple narrative:

function recountTale(animal: string | number) {
    if (typeof animal === 'string') {
        return `The tale unfolds: ${animal}`;
    }
    return `Numerical lore of: ${animal}`;
}

Here, the typeof guard is our sieve, separating string fish from number fish, as it were.

User-Defined Type Guards

Consider a more complex ecosystem with creatures of varied fables, a time when plain typeof checks fall short, and user-defined guards astutely ascertain the species:

interface Raconteur {
    spinYarn: () => string;
}

function isRaconteur(animal: any): animal is Raconteur {
    return (animal as Raconteur).spinYarn !== undefined;
}

function regaleTales(animal: unknown): string {
    if (isRaconteur(animal)) {
        return animal.spinYarn();
    }
    throw new Error('Unable to fathom the tales of this entity.');
}

Our isRaconteur function navigates through treacherous waters by confirming the presence of spinYarn, assuring we hearken only to genuine raconteurs.

More Advanced Types

Snags and sawyers await as we delve deeper. Imagine discriminating pedigrees amidst a menagerie with discriminated unions:

type Animal = { type: 'cat', meow: () => void; } | { type: 'dog', bark: () => void; };

function communicate(animal: Animal) {
    if (animal.type === 'cat') {
        animal.meow();
    } else if (animal.type === 'dog') {
        animal.bark();
    }
}

Herewith, our voyage dictates a keen eye for the ‘type’ property, allowing us to correctly invoke either a ‘meow’ or a ‘bark’ based on the critter’s lineage.

Control Flow Analysis

Mississippi’s waters churn just as TypeScript evaluates code paths. Assignment operations and control structures narrow types subsequent to code execution:

function ventureForth(foe: string | string[]) {
    let tale: string;
    if (Array.isArray(foe)) {
        tale = foe.join(' and ');
    } else {
        // TypeScript knows 'foe' must be a string here
        tale = foe.toUpperCase();
    }
    // Continue the adventure...
}

Thus, our course adjusts swiftly, reassigning ‘tale’ as befits the foe’s guise as either a single antagonist or a legion.

Conclusion

As Twain mastered the pen and piloted the steamboat, this guide aimed to impart mastery over TypeScript’s type narrowing, a skill as critical as navigation upon the mighty Mississippi. Cast these techniques into your arduous coder’s waters, to chart through TypeScript’s type checks with the ease of a seasoned captain.