Mastering Union Types and Intersection Types


Mastering Union Types and Intersection Types in TypeScript

Introduction

TypeScript, developed by Microsoft, is a typed superset of JavaScript that compiles to plain JavaScript. One of the key features of TypeScript is its type system, which makes it easier to catch bugs and enhance the development experience. Among the mechanisms in TypeScript’s type system, Union Types and Intersection Types stand out as powerful tools for creating flexible and robust code. This article will delve into what Union Types and Intersection Types are, how to use them, and provide examples to help beginners master these concepts.

Union Types

Union Types allow a variable to hold more than one type. It’s like saying, “This variable can be of type X or type Y.” This makes your code more flexible and enables you to define variables that can work with multiple types of values.

Syntax of Union Types

In TypeScript, you can define a Union Type using the pipe (|) symbol between the types. Here’s an example:


let value: string | number;
value = "Hello"; // Valid
value = 10; // Valid
value = true; // Invalid

In this example, the value variable can hold either a string or a number, but not a boolean.

Practical Use Cases for Union Types

Union Types can be particularly useful in scenarios where a function can accept multiple types of parameters or return different types of results based on its logic.

Example: Function with Union Type Parameters


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

printId("12345"); // Output: Your ID is: 12345
printId(67890); // Output: Your ID is: 67890

Example: Function Returning Union Type


function getValue(flag: boolean): string | number {
    if (flag) {
        return "Flag is true";
    } else {
        return 0;
    }
}

In both examples, the functions can handle multiple types, making the code more versatile.

Type Guards with Union Types

When working with Union Types, it’s often necessary to determine the actual type of a variable at runtime. TypeScript provides type guards to help with this.

Using typeof for Primitive Types


function display(value: string | number) {
    if (typeof value === "string") {
        console.log(`String value: ${value}`);
    } else {
        console.log(`Number value: ${value}`);
    }
}

display("Hello"); // Output: String value: Hello
display(123);     // Output: Number value: 123

Using instanceof for Object Types


class Car {
    drive() {
        console.log("Driving a car...");
    }
}

class Bike {
    ride() {
        console.log("Riding a bike...");
    }
}

function move(vehicle: Car | Bike) {
    if (vehicle instanceof Car) {
        vehicle.drive();
    } else {
        vehicle.ride();
    }
}

let myCar = new Car();
let myBike = new Bike();

move(myCar); // Output: Driving a car...
move(myBike); // Output: Riding a bike...

Intersection Types

Intersection Types allow you to combine multiple types into one. It’s like saying, “This variable must be of all these types at once.” This makes your code more expressive, as you can aggregate multiple type definitions into a single entity that carries all their properties.

Syntax of Intersection Types

In TypeScript, you can define an Intersection Type using the ampersand (&) symbol between the types. Here’s an example:


type Person = {
    name: string;
    age: number;
};

type Employee = {
    employeeId: number;
    position: string;
};

type EmployeeDetails = Person & Employee;

let employee: EmployeeDetails = {
    name: "John Doe",
    age: 30,
    employeeId: 12345,
    position: "Software Engineer"
};

In this example, the employee variable must satisfy both the Person and Employee types.

Practical Use Cases for Intersection Types

Intersection Types can be particularly useful in scenarios where you want to compose different types to create a new type with combined properties and methods.

Example: Combining Multiple Interfaces


interface Drivable {
    drive(): void;
}

interface Flyable {
    fly(): void;
}

type FlyingCar = Drivable & Flyable;

class FutureCar implements FlyingCar {
    drive() {
        console.log("Driving a future car...");
    }

    fly() {
        console.log("Flying a future car...");
    }
}

let myFutureCar = new FutureCar();
myFutureCar.drive(); // Output: Driving a future car...
myFutureCar.fly(); // Output: Flying a future car...

Example: Detailed Object Descriptions

Intersection Types can help you create more detailed object descriptions by merging various type definitions.


type Address = {
    street: string;
    city: string;
};

type ContactInfo = {
    email: string;
    phone: string;
};

type UserProfile = Address & ContactInfo & {
    name: string;
};

let user: UserProfile = {
    name: "Alice",
    street: "123 Main St",
    city: "Wonderland",
    email: "alice@example.com",
    phone: "123-456-7890"
};

Combining Union Types and Intersection Types

TypeScript allows you to combine Union Types and Intersection Types to create even more complex type definitions. This can provide a balance between flexibility and strictness.

Example: Complex Type Definitions


type Admin = {
    adminId: number;
    privileges: string[];
};

type User = {
    userId: number;
    username: string;
};

type AdminUser = Admin & User;

type PersonType = string | number | boolean;

function describePerson(person: PersonType | AdminUser) {
    if (typeof person === "object" && 'adminId' in person) {
        console.log(`Admin User: ${person.username}, Privileges: ${person.privileges.join(", ")}`);
    } else {
        console.log(`Person: ${person}`);
    }
}

let admin: AdminUser = {
    adminId: 101,
    privileges: ["manage-users", "edit-settings"],
    userId: 1,
    username: "adminUser"
};

describePerson(admin); // Output: Admin User: adminUser, Privileges: manage-users, edit-settings
describePerson("Charlie"); // Output: Person: Charlie

Conclusion

Union Types and Intersection Types in TypeScript provide powerful tools for creating flexible yet robust type definitions. They enhance the ability to write code that is both versatile and type-safe, which is a significant advantage when working on large projects or complex applications. Understanding and mastering these concepts will undoubtedly elevate your TypeScript skills and allow you to write more efficient and maintainable code.

For further reading and to deepen your knowledge, consider visiting the Wikipedia article on TypeScript.


Leave a Reply

Your email address will not be published. Required fields are marked *