Union, Intersection, and Literal Types

Introduction

Real-world values often belong to one of several shapes or combine multiple capabilities. TypeScript models that with union (|), intersection (&), and literal types that pin values to exact strings or numbers. These building blocks power discriminated unions, configuration flags, and flexible APIs.

Prerequisites

Union Types: A or B

A value may be one of several types:

typescript
function printId(id: string | number): void {
  console.log(`ID: ${id}`);
}
 
printId(101);
printId("abc-101");

Code explanation:

  • string | number accepts either primitive
  • You must narrow before using type-specific methods (next chapter)

Union of object shapes:

typescript
type Success = { ok: true; data: string };
type Failure = { ok: false; error: string };
type Result = Success | Failure;
 
function handle(result: Result): void {
  if (result.ok) {
    console.log(result.data);
  } else {
    console.log(result.error);
  }
}

ok acts as a discriminant—TypeScript narrows inside each branch.

Intersection Types: A and B

Combine multiple types into one requirement:

typescript
type Named = { name: string };
type Aged = { age: number };
 
type Person = Named & Aged;
 
const person: Person = {
  name: "Ada",
  age: 30,
};

Code explanation:

  • Person must satisfy all members of Named and Aged
  • Conflicting property types must be compatible (same or subtype)

Intersection with interfaces

Literal Types

Exact values as types:

typescript
type Direction = "north" | "south" | "east" | "west";
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
 
function move(dir: Direction): void {
  console.log(`Moving ${dir}`);
}
 
move("north");
// move("up");  // Error

Code explanation:

  • String literal unions model fixed sets of choices
  • Numeric literal unions model finite numeric domains

const and literal inference

typescript
const theme = "dark" as const;
// theme: "dark" (not string)
 
const config = {
  mode: "production",
  port: 3000,
} as const;
// config.mode: "production", config.port: 3000

as const makes properties readonly and literal-typed.

Unions vs Intersections

OperatorMeaningExample mental model
| unionOne of“string or number”
& intersectionAll of“named and aged”
typescript
type A = { a: string };
type B = { b: number };
 
type AB = A & B;
type AorB = A | B;

AB requires both a and b. AorB requires either shape (often too loose for objects unless narrowed).

Practical Patterns

Status and HTTP-style codes

typescript
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
 
function request(method: HttpMethod, path: string): void {
  console.log(`${method} ${path}`);
}

Configuration with unions

Discriminant type narrows config in each branch.

Combining unions and intersections

typescript
type Admin = { role: "admin"; permissions: string[] };
type Member = { role: "member" };
type Guest = { role: "guest" };
 
type User = (Admin | Member | Guest) & { id: string; name: string };
 
function describe(user: User): string {
  if (user.role === "admin") {
    return `${user.name} (admin, ${user.permissions.length} permissions)`;
  }
  return `${user.name} (${user.role})`;
}

When to Use type vs interface

Unions and most literal combinations require type:

typescript
type ID = string | number;
type Theme = "light" | "dark";

interface cannot express A | B directly.

Common Mistakes

MistakeProblemFix
Wide string instead of literalsLost exhaustivenessUse union of literals
Union of unrelated objects without discriminantHard to narrowAdd shared field like kind or type
A & B with conflicting typesError or neverAlign property types
Too many union membersUnreadableGroup or split types

FAQ

Union vs enum?

Enums name constants at runtime; string literal unions are lightweight and tree-shake friendly. Both work; teams often prefer unions for new code.

Can unions include null?

Yes: string | null. Prefer explicit unions over implicit null everywhere (nullability chapter).

What is never in unions?

A | never simplifies to A. never appears when intersections conflict or exhaustive checks fail.

Is intersection the same as multiple implements?

Similar idea for types. Classes use implements A, B; type aliases use A & B.

How many union members is OK?

Dozens of string literals are fine (for example HTTP status families split across types). If logic explodes, use maps or split modules.

Does order matter in A | B?

No. string | number equals number | string.