Menu
Courses / TypeScript Basics / union intersection and narrowing

union intersection and narrowing

09 / 10 Part of TypeScript Basics

Union Types Recap


A union type allows a variable to be one of several possible types, joined with a pipe |.


let id: string | number;

id = 101;      // valid
id = "abc123"; // valid


Union Types in Function Parameters


function printId(id: string | number): void {
    console.log(`Your ID is: ${id}`);
}

printId(101);
printId("abc123");


The Problem: Not All Methods Are Shared


function printId(id: string | number): void {
    console.log(id.toUpperCase()); // Error: 'toUpperCase' does not exist on type 'number'
}

Since id could be a number, and numbers don't have a toUpperCase method, TypeScript blocks this. This is where type narrowing comes in.



Narrowing with typeof


function printId(id: string | number): void {
    if (typeof id === "string") {
        console.log(id.toUpperCase()); // TypeScript knows id is a string here
    } else {
        console.log(id.toFixed(2)); // TypeScript knows id is a number here
    }
}


Narrowing with Array.isArray()


function printAll(value: string | string[]): void {
    if (Array.isArray(value)) {
        value.forEach(v => console.log(v));
    } else {
        console.log(value);
    }
}


Narrowing with in


The in operator checks whether a property exists on an object — useful for narrowing between different object shapes.


interface Cat {
    meow(): void;
}

interface Dog {
    bark(): void;
}

function makeSound(animal: Cat | Dog): void {
    if ("meow" in animal) {
        animal.meow();
    } else {
        animal.bark();
    }
}


Discriminated Unions


A powerful pattern where each type in a union shares a common literal property (a "tag") that identifies which variant it is.


interface Circle {
    kind: "circle";
    radius: number;
}

interface Square {
    kind: "square";
    sideLength: number;
}

type Shape = Circle | Square;

function getArea(shape: Shape): number {
    switch (shape.kind) {
        case "circle":
            return Math.PI * shape.radius ** 2;
        case "square":
            return shape.sideLength ** 2;
    }
}

getArea({ kind: "circle", radius: 5 });      // 78.53...
getArea({ kind: "square", sideLength: 4 });  // 16


    Why this works well: TypeScript automatically narrows shape to the correct interface inside each case, based on the value of the shared kind property.


Intersection Types


While a union means "one type OR another," an intersection (&) means "combine every type into one" — the result must satisfy all of them at once.


interface Person {
    name: string;
}

interface Employee {
    employeeId: number;
}

type StaffMember = Person & Employee;

const staff: StaffMember = {
    name: "Alice",
    employeeId: 1001
}; // must include properties from BOTH interfaces


Union vs Intersection — Quick Comparison



   
   
       
       
       
   
Union (|)Intersection (&)
Value can be ONE of the listed typesValue must satisfy ALL listed types at once
Common for "either this or that" valuesCommon for merging multiple interfaces together
Example: string | numberExample: Person & Employee



    Coming up next: We wrap up the course with utility types and a look at how TypeScript projects are configured.