Skip to content

TypeScript Union Types

Union types are a powerful feature in TypeScript that allow a variable to be one of several types. We use the pipe symbol (|) to separate each type.

What are Union Types?

When you want a variable, function parameter, or object property to accept values of different types, union types are very useful.

Basic Example

typescript
let value: string | number;

value = "Hello, TypeScript"; // OK
value = 123;                 // OK

// value = true; // Error: Type 'boolean' is not assignable to type 'string | number'.

In this example, the value variable can be either a string or a number.

Using Union Types

When a value's type is a union type, we can only access members that are common to all types in the union.

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

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

function getSmallPet(): Fish | Bird {
    // ...return a Fish or Bird instance
    return { fly: () => {}, layEggs: () => {} };
}

let pet = getSmallPet();

// We can call layEggs() because it's a common method of Fish and Bird
pet.layEggs();

// But we can't directly call fly() or swim() because TypeScript isn't sure which type pet is
// pet.fly(); // Error: Property 'fly' does not exist on type 'Fish | Bird'.

Type Narrowing

To use methods or properties specific to a particular type, we need to tell TypeScript the current variable's specific type. This process is called "type narrowing" or "type guards."

There are multiple ways to achieve type narrowing:

1. typeof Type Guard

For primitive types, typeof is the most common type guard.

typescript
function printId(id: number | string) {
    if (typeof id === "string") {
        // In this code block, TypeScript knows id is string type
        console.log(id.toUpperCase());
    } else {
        // Here, id must be number type
        console.log(id);
    }
}

printId("abc"); // ABC
printId(123); // 123

2. instanceof Type Guard

instanceof is used to check if an instance belongs to a certain class.

typescript
class Dog { bark() { console.log("Woof!"); } }
class Cat { meow() { console.log("Meow!"); } }

function makeSound(animal: Dog | Cat) {
    if (animal instanceof Dog) {
        // TypeScript knows animal is Dog type
        animal.bark();
    } else {
        // TypeScript knows animal is Cat type
        animal.meow();
    }
}

3. in Operator Type Guard

The in operator can check if an object has a certain property.

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

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

function move(animal: Bird | Fish) {
    if ("fly" in animal) {
        // TypeScript knows animal is Bird type
        return animal.fly();
    }
    // TypeScript knows animal is Fish type
    return animal.swim();
}

Literal Union Types

Union types can be used not only for types but also for specific literal values. This can be used to restrict a variable to only a few specific string or number values.

typescript
type Alignment = "left" | "right" | "center";

let textAlign: Alignment;

textAlign = "center"; // OK
textAlign = "left";   // OK
// textAlign = "justify"; // Error: Type '"justify"' is not assignable to type 'Alignment'.


function setMargin(margin: 0 | 10 | 20) {
    // ...
}

setMargin(10); // OK
// setMargin(5); // Error: Argument of type '5' is not assignable to parameter of type '0 | 10 | 20'.

This pattern is very useful in API design, providing functionality similar to enums but more lightweight.

Content is for learning and research only.