TypeScript Union Types: A Comprehensive Tutorial with Code Examples

Union types in TypeScript allow for increased flexibility by enabling a variable to hold more than one type of value.

Instead of restricting a variable to a single type (e.g., string, number, or boolean), a union type lets you define a combination of types.

This tutorial will walk you through the concept of union types in TypeScript, along with practical examples to illustrate their usage.

1. What Are Union Types?

In TypeScript, a union type allows a variable to hold values from two or more distinct types. Union types are declared by using the vertical bar | between the types.

Syntax:

let variable: type1 | type2 | type3;

Where type1, type2, and type3 are valid TypeScript types.

Example:

let value: string | number;
value = "Hello";  // Valid
value = 42;       // Valid
value = true;     // Error: Type 'boolean' is not assignable to type 'string | number'

In this example, value can either be a string or a number, but it cannot be a boolean.

2. Simple Examples of Union Types

Union types provide a convenient way to handle values that can have different types without losing type safety.

Example 1: Basic Union Types

let id: number | string;
id = 101;           // Valid
id = "ABC123";      // Valid
id = true;          // Error: Type 'boolean' is not assignable to type 'string | number'

Example 2: Function Parameters with Union Types

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

printId(123);       // Valid
printId("ABC123");  // Valid
printId(true);      // Error: Argument of type 'boolean' is not assignable to parameter of type 'string | number'

3. Type Narrowing

TypeScript allows us to narrow down the type within a union using conditional logic like typeof or instanceof. This ensures the correct type is used within a block of code.

Example 3: Type Narrowing Using typeof

function printDetails(value: string | number): void {
    if (typeof value === "string") {
        console.log("String value: " + value.toUpperCase());
    } else {
        console.log("Number value: " + (value * 2));
    }
}

printDetails("Hello");  // Output: String value: HELLO
printDetails(21);       // Output: Number value: 42

In this example, TypeScript checks the type of value at runtime and narrows the union type to either string or number inside the respective if and else blocks.

4. Union Types with Functions

Union types are especially useful in function parameters and return types, enabling you to create more flexible functions.

Example 4: Function with Union Return Types

function calculate(value: number | string): number | string {
    if (typeof value === "number") {
        return value * 10;
    } else {
        return `Value is: ${value}`;
    }
}

console.log(calculate(5));        // Output: 50
console.log(calculate("Test"));   // Output: Value is: Test

This function can return either a number or a string based on the input type.

5. Union Types in Arrays

TypeScript allows you to declare arrays that can hold multiple types of values using union types.

Example 5: Arrays with Union Types

let mixedArray: (number | string)[] = [1, "Hello", 2, "World"];
mixedArray.push(3);        // Valid
mixedArray.push("Test");   // Valid
mixedArray.push(true);     // Error: Argument of type 'boolean' is not assignable to parameter of type 'string | number'

In this example, mixedArray can contain both number and string values but cannot hold any other types.

6. Union Types with Objects

You can also use union types to specify objects with different structures. This is particularly useful when you need to handle different object types in a flexible manner.

Example 6: Union Types with Objects

type Admin = {
    role: string;
    accessLevel: number;
};

type User = {
    username: string;
    isLoggedIn: boolean;
};

function printUserInfo(person: Admin | User): void {
    if ('role' in person) {
        console.log(`Admin Role: ${person.role}, Access Level: ${person.accessLevel}`);
    } else {
        console.log(`User: ${person.username}, Logged In: ${person.isLoggedIn}`);
    }
}

const admin: Admin = { role: "SuperAdmin", accessLevel: 5 };
const user: User = { username: "JohnDoe", isLoggedIn: true };

printUserInfo(admin); // Output: Admin Role: SuperAdmin, Access Level: 5
printUserInfo(user);  // Output: User: JohnDoe, Logged In: true

Here, the function handles both Admin and User object types by using the in operator to check which properties are available and then performs the relevant actions.

7. Common Use Cases for Union Types

Union types are useful in various scenarios where flexibility is required. Some common use cases include:

Example 7: Handling API Responses

APIs often return different types of data based on different conditions (e.g., success or error messages).

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

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

function handleApiResponse(response: SuccessResponse | ErrorResponse): void {
    if (response.status === "success") {
        console.log("Data: " + response.data);
    } else {
        console.log("Error: " + response.message);
    }
}

const successResponse: SuccessResponse = { status: "success", data: "Data retrieved successfully" };
const errorResponse: ErrorResponse = { status: "error", message: "Failed to retrieve data" };

handleApiResponse(successResponse);  // Output: Data: Data retrieved successfully
handleApiResponse(errorResponse);    // Output: Error: Failed to retrieve data

In this example, the function gracefully handles both success and error responses using union types.

Conclusion

Union types in TypeScript are a valuable tool for increasing flexibility while maintaining type safety. They allow a variable, parameter, or return type to accept multiple types, making your code more versatile.

By using type narrowing, you can ensure that the correct type is used in any given situation. From handling API responses to working with arrays and objects, union types are an essential feature in TypeScript for writing robust, type-safe applications.

Related posts

TypeScript null & undefined: A Comprehensive Tutorial with Code Examples

TypeScript Casting: A Comprehensive Tutorial with Code Examples

TypeScript Functions: A Comprehensive Tutorial with Code Examples