thecodex.expert · The Codex Family of Knowledge
TypeScript

Type Narrowing

TypeScript watches your if statements — a plain typeof check inside one is enough for it to know exactly which member of a union you’re holding, with zero extra syntax.

TypeScript 6.0 typeof, instanceof, in Last verified:
Canonical Definition

TypeScript's control-flow analysis watches conditional checks and progressively narrows a variable's type within each branch — a typeof check narrows a union to one specific primitive type, instanceof narrows to a specific class, and the in operator narrows based on whether a named property exists on the value. This happens automatically from ordinary JavaScript runtime checks; no special TypeScript-only syntax is required.

typeof narrowing

Inside each branch of the check, TypeScript treats value as exactly the narrowed type — no cast or assertion needed.

TypeScripttypeof_narrowing.ts
function format(value: string | number) {
    if (typeof value === "string") {
        return value.toUpperCase();   // TypeScript knows: value is string here
    }
    return value.toFixed(2);           // TypeScript knows: value is number here
}

instanceof and in narrowing

instanceof narrows to a specific class; in narrows based on whether a property exists, useful for distinguishing object shapes without a class hierarchy at all.

TypeScriptinstanceof_in.ts
class Dog { bark() { return "Woof"; } }
class Cat { meow() { return "Meow"; } }

function speak(animal: Dog | Cat) {
    if (animal instanceof Dog) {
        return animal.bark();     // narrowed to Dog
    }
    return animal.meow();          // narrowed to Cat
}

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

function move(pet: Fish | Bird) {
    if ("swim" in pet) {
        pet.swim();    // narrowed to Fish
    } else {
        pet.fly();      // narrowed to Bird
    }
}

Discriminated unions: the cleanest pattern

A shared literal-type property (here, kind) lets TypeScript narrow an entire union precisely from one check, and a switch over that property gets exhaustiveness checking almost for free — this is widely considered the most robust narrowing pattern for anything beyond a couple of simple cases.

TypeScriptdiscriminated_union.ts
type Circle = { kind: "circle"; radius: number };
type Square = { kind: "square"; side: number };
type Shape = Circle | Square;

function area(shape: Shape): number {
    switch (shape.kind) {
        case "circle": return Math.PI * shape.radius ** 2;   // narrowed to Circle
        case "square": return shape.side ** 2;                 // narrowed to Square
    }
}

Sources

1
Microsoft. TypeScript Handbook, "Narrowing", typescriptlang.org/docs/handbook/2/narrowing.html.