TypeScript Union Types

Allow multiple type possibilities

🔗 What are Union Types?

Union types allow a variable to hold values of multiple types using the pipe (|) operator. They provide flexibility while maintaining type safety, letting you specify that a value can be one of several types.


// Simple union type
let id: string | number;

id = "abc123";  // Valid
id = 456;       // Also valid
                                    

Union Type Patterns

🔤

Primitive Unions

Combine basic types

type ID = 
    string | number;
type Value = 
    string | boolean;
📝

Literal Unions

Specific value options

type Status = 
    "active" | 
    "inactive" | 
    "pending";
🎯

Object Unions

Different object shapes

type Response = 
    Success | Error;
    
type Success = {
    data: string;
};
🔍

Type Guards

Check union types

if (typeof x 
    === "string") {
    // x is string
}

🔹 Basic Union Types

Combine primitive types:

let value: string | number;

value = "Hello";
console.log(`String value: ${value}`);

value = 42;
console.log(`Number value: ${value}`);

// value = true; // Error: boolean not in union

Output:

String value: Hello

Number value: 42

🔹 Union Type in Functions

Function parameters can accept multiple types:

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

printId(101);
printId("ABC123");

function formatValue(value: string | number | boolean) {
    return `Value: ${value}`;
}

console.log(formatValue("text"));
console.log(formatValue(123));
console.log(formatValue(true));

Output:

Your ID is: 101

Your ID is: ABC123

Value: text

Value: 123

Value: true

🔹 Type Guards

Check which type you're working with:

function processValue(value: string | number) {
    if (typeof value === "string") {
        console.log(`String length: ${value.length}`);
    } else {
        console.log(`Number doubled: ${value * 2}`);
    }
}

processValue("Hello");
processValue(10);

Output:

String length: 5

Number doubled: 20

🔹 Literal Union Types

Restrict to specific values:

type Direction = "north" | "south" | "east" | "west";
type Status = "success" | "error" | "loading";

let heading: Direction = "north";
let requestStatus: Status = "loading";

function move(direction: Direction) {
    console.log(`Moving ${direction}`);
}

move("north");
move("east");
// move("up"); // Error: "up" not in union

Output:

Moving north

Moving east

🔹 Union of Object Types

Combine different object structures:

type Success = {
    status: "success";
    data: string;
};

type Error = {
    status: "error";
    message: string;
};

type Response = Success | Error;

function handleResponse(response: Response) {
    if (response.status === "success") {
        console.log(`Data: ${response.data}`);
    } else {
        console.log(`Error: ${response.message}`);
    }
}

handleResponse({ status: "success", data: "User created" });
handleResponse({ status: "error", message: "Not found" });

Output:

Data: User created

Error: Not found

🔹 Array Union Types

Arrays with multiple value types:

let mixedArray: (string | number)[] = [1, "two", 3, "four"];

mixedArray.forEach(item => {
    console.log(`Item: ${item}, Type: ${typeof item}`);
});

type StringOrNumberArray = string[] | number[];
let stringArray: StringOrNumberArray = ["a", "b", "c"];
let numberArray: StringOrNumberArray = [1, 2, 3];

Output:

Item: 1, Type: number

Item: two, Type: string

Item: 3, Type: number

Item: four, Type: string

🔹 Discriminated Unions

Use a common property to distinguish types:

type Circle = {
    kind: "circle";
    radius: number;
};

type Square = {
    kind: "square";
    sideLength: number;
};

type Shape = Circle | Square;

function getArea(shape: Shape): number {
    if (shape.kind === "circle") {
        return Math.PI * shape.radius ** 2;
    } else {
        return shape.sideLength ** 2;
    }
}

console.log(`Circle area: ${getArea({ kind: "circle", radius: 5 }).toFixed(2)}`);
console.log(`Square area: ${getArea({ kind: "square", sideLength: 4 })}`);

Output:

Circle area: 78.54

Square area: 16

🔹 Practical Example

Building a flexible API response handler:

type LoadingState = {
    status: "loading";
};

type SuccessState = {
    status: "success";
    data: { id: number; name: string }[];
};

type ErrorState = {
    status: "error";
    error: string;
};

type ApiState = LoadingState | SuccessState | ErrorState;

function displayState(state: ApiState) {
    switch (state.status) {
        case "loading":
            console.log("Loading...");
            break;
        case "success":
            console.log(`Loaded ${state.data.length} items`);
            break;
        case "error":
            console.log(`Error: ${state.error}`);
            break;
    }
}

displayState({ status: "loading" });
displayState({ status: "success", data: [{ id: 1, name: "Item" }] });
displayState({ status: "error", error: "Network failed" });

Output:

Loading...

Loaded 1 items

Error: Network failed

🧠 Test Your Knowledge

Which operator is used to create union types?