Start Coding

Topics

Discriminated Unions in TypeScript

Discriminated unions are a powerful feature in TypeScript that allow you to work with multiple types in a type-safe manner. They combine Union Types with a common, literal property that distinguishes between the union members.

Understanding Discriminated Unions

A discriminated union, also known as a tagged union or sum type, consists of several types that share a common, discriminant property. This property acts as a "tag" to identify which specific type is being used.

Syntax and Usage

To create a discriminated union, define interfaces or types with a shared, literal property:

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; sideLength: number }
  | { kind: "rectangle"; width: number; height: number };

In this example, kind is the discriminant property. It helps TypeScript narrow down the specific type within the union.

Working with Discriminated Unions

You can use discriminated unions with switch statements or if-else chains for type-safe operations:

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

Benefits and Use Cases

Discriminated unions offer several advantages:

  • Type safety: The compiler ensures you handle all possible cases.
  • Code organization: They provide a clear structure for related types.
  • Improved readability: The discriminant property makes the code self-documenting.

Common use cases include:

  • State management in applications
  • Handling different types of API responses
  • Modeling complex domain entities

Best Practices

  • Use a clear, descriptive name for the discriminant property (e.g., kind, type, or variant).
  • Prefer string literals for the discriminant values to improve readability.
  • Leverage TypeScript Utility Types like Extract to work with specific members of the union.

Advanced Example: State Management

Here's a more complex example demonstrating how discriminated unions can be used for state management in a simple application:

type AppState =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: string }
  | { status: "error"; error: Error };

function handleState(state: AppState) {
  switch (state.status) {
    case "idle":
      console.log("Application is idle");
      break;
    case "loading":
      console.log("Loading data...");
      break;
    case "success":
      console.log(`Data loaded: ${state.data}`);
      break;
    case "error":
      console.error(`Error occurred: ${state.error.message}`);
      break;
  }
}

This pattern ensures type safety while handling different application states, making it easier to manage complex state transitions and associated data.

Conclusion

Discriminated unions are a cornerstone of TypeScript's type system, enabling developers to write more robust and maintainable code. By combining the flexibility of union types with the safety of literal types, they provide a powerful tool for modeling complex data structures and application states.

To further enhance your TypeScript skills, explore related concepts such as Intersection Types and Conditional Types.