Intermediate Exercism • typescript

Unions & Intersections

Lesson Overview

Master type composition by combining types with Union and Intersection operators.

Unions & Intersections

In TypeScript, you can compose new types by combining existing ones. The two primary ways to do this are Union Types and Intersection Types.

Union Types (|)

A union type allows a value to be one of several types. Think of it as an “OR” relationship.

type ID = string | number;

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

printId(101);     // OK
printId("ABC");   // OK

Narrowing

When working with union types, you often need to narrow the type to perform type-specific operations.

function formatId(id: string | number) {
  if (typeof id === "string") {
    return id.toUpperCase(); // In this block, id is 'string'
  }
  return id.toFixed(2); // In this block, id is 'number'
}

Intersection Types (&)

An intersection type combines multiple types into one. The new type will have all the properties of every type in the intersection. Think of it as an “AND” relationship.

interface ErrorHandling {
  success: boolean;
  error?: { message: string };
}

interface ArtData {
  title: string;
  artist: string;
}

type ArtResponse = ArtData & ErrorHandling;

const response: ArtResponse = {
  title: "The Starry Night",
  artist: "Vincent van Gogh",
  success: true
};

Discriminated Unions

A common pattern in TypeScript is to use a “tag” or “discriminant” property to distinguish between different types in a union.

interface Circle {
  kind: "circle";
  radius: number;
}

interface Square {
  kind: "square";
  sideLength: number;
}

type Shape = Circle | Square;

function getArea(shape: Shape) {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "square":
      return shape.sideLength ** 2;
  }
}

This pattern is extremely helpful for handling complex state (like API states: Loading, Success, Error) in a type-safe way.