intermediate Step 7 of 18

Type Aliases and Union Types

TypeScript Programming

Type Aliases and Union Types

Type aliases and union types are two of TypeScript's most versatile features for creating expressive, flexible type definitions. A type alias gives a name to any type expression, making complex types reusable and readable. Union types allow a value to be one of several types, enabling you to model real-world data that can take multiple forms. Together with intersection types, these features let you compose sophisticated type definitions from simpler building blocks without the ceremony of class hierarchies.

Type Aliases

The type keyword creates an alias — a new name for an existing type. Unlike interfaces, type aliases can represent primitives, unions, tuples, and any other type expression.

// Alias for a primitive
type ID = string | number;

// Alias for an object shape
type Point = {
  x: number;
  y: number;
};

// Alias for a function type
type Formatter = (input: string) => string;

// Alias for a tuple
type Coordinate = [number, number, number?]; // x, y, optional z

// Usage
let userId: ID = "abc-123";
userId = 42; // also valid

const origin: Point = { x: 0, y: 0 };

const toUpper: Formatter = (s) => s.toUpperCase();

const pos: Coordinate = [10, 20];

Union Types

A union type describes a value that can be one of several types, using the pipe | operator. Union types are essential for modeling data that legitimately varies in shape, such as API responses, form inputs, or state machines.

// Simple union
type StringOrNumber = string | number;

function display(value: StringOrNumber): string {
  if (typeof value === "string") {
    return value.toUpperCase();
  }
  return value.toFixed(2);
}

// Literal union — restricts to specific values
type Status = "pending" | "active" | "archived";
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";

function setStatus(status: Status): void {
  console.log(`Status changed to: ${status}`);
}
setStatus("active");   // OK
// setStatus("unknown"); // Error: '"unknown"' is not assignable to type 'Status'

// Discriminated unions — powerful pattern for state modeling
type LoadingState = { status: "loading" };
type SuccessState = { status: "success"; data: string[] };
type ErrorState = { status: "error"; message: string };

type RequestState = LoadingState | SuccessState | ErrorState;

function renderState(state: RequestState): string {
  switch (state.status) {
    case "loading":
      return "Loading...";
    case "success":
      return `Got ${state.data.length} items`; // data is available here
    case "error":
      return `Error: ${state.message}`;        // message is available here
  }
}

Intersection Types

Intersection types combine multiple types into one using the ampersand & operator. The resulting type has all properties from all constituent types. This is useful for composing objects from multiple sources.

type HasName = { name: string };
type HasAge = { age: number };
type HasEmail = { email: string };

// Intersection: must have all properties from all types
type Person = HasName & HasAge & HasEmail;

const person: Person = {
  name: "Alice",
  age: 30,
  email: "alice@example.com",
};

// Practical example: extending API response types
type ApiResponse = {
  statusCode: number;
  timestamp: string;
};

type UserResponse = ApiResponse & {
  user: { id: number; name: string };
};

const response: UserResponse = {
  statusCode: 200,
  timestamp: new Date().toISOString(),
  user: { id: 1, name: "Bob" },
};

Type Aliases vs Interfaces

Both can describe object shapes, but they differ in key ways. Interfaces support declaration merging and are extendable with extends. Type aliases support unions, intersections, mapped types, and conditional types. In practice, use interfaces for object shapes that might be extended and type aliases for everything else.

// Interface — can be extended and merged
interface Animal {
  name: string;
}
interface Animal {
  legs: number; // declaration merging adds this to Animal
}

// Type alias — cannot be merged, but supports unions
type Pet = Animal & { owner: string };
type FarmAnimal = Animal | { species: string; farmId: number };
Tip: Discriminated unions (also called tagged unions) are one of TypeScript's most powerful patterns. Always include a literal type or status field in each variant to enable exhaustive switch statements and automatic type narrowing.

Key Takeaways

  • Type aliases create reusable names for any type expression including primitives, objects, functions, and tuples.
  • Union types (A | B) allow a value to be one of several types.
  • Literal unions like "success" | "error" restrict values to specific string or number constants.
  • Intersection types (A & B) combine multiple types, requiring all properties from each.
  • Discriminated unions with a shared literal field enable powerful pattern matching and exhaustive checking.