TypeScript Unions & Intersections

Unions and intersections are how you compose types together. They are the backbone of flexible, type-safe APIs.

Union Types (OR)

A value can be one of several types:

TypeScript
type Status = "loading" | "success" | "error";
type ID = string | number;
type Nullish = null | undefined;

Union types are especially powerful with string literals:

TypeScript
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";

function request(method: HttpMethod, url: string) {
  console.log(`${method} ${url}`);
}

request("GET", "/api/users");    // ✅
request("PATCH", "/api/users");  // ❌ Error: not a valid method

Discriminated Unions

The most powerful pattern in TypeScript. Add a common type (or kind) field to each variant:

TypeScript
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "rectangle"; width: number; height: number }
  | { kind: "triangle"; base: number; height: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "rectangle":
      return shape.width * shape.height;
    case "triangle":
      return 0.5 * shape.base * shape.height;
  }
}

TypeScript narrows the type inside each case — you only have access to the properties of that specific variant.

Intersection Types (AND)

Combine multiple types with & — the result has all properties:

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

type Person = HasName & HasAge & HasEmail;

const person: Person = {
  name: "Sita",
  age: 21,
  email: "sita@example.com",
};

Union vs Intersection

Operator Meaning Result
| (union) A or B Has properties of A or B
& (intersection) A and B Has properties of A and B
TypeScript
type A = { x: number };
type B = { y: string };

type Union = A | B;      // { x: number } OR { y: string }
type Intersection = A & B; // { x: number } AND { y: string }

Type Narrowing

TypeScript narrows types through control flow:

TypeScript
function process(value: string | number) {
  if (typeof value === "string") {
    // TypeScript knows value is string here
    return value.toUpperCase();
  }
  // TypeScript knows value is number here
  return value.toFixed(2);
}

The in Operator

TypeScript
type Fish = { swim: () => void };
type Bird = { fly: () => void };

function move(animal: Fish | Bird) {
  if ("swim" in animal) {
    animal.swim(); // TypeScript knows it's Fish
  } else {
    animal.fly(); // TypeScript knows it's Bird
  }
}

Type Guards

Custom functions that narrow types:

TypeScript
function isString(value: unknown): value is string {
  return typeof value === "string";
}

function process(value: string | number) {
  if (isString(value)) {
    console.log(value.toUpperCase()); // safe
  }
}

Tips

  • Discriminated unions are the #1 pattern in TypeScript — learn them well.
  • Use typeof and in for basic narrowing.
  • Create custom type guards (value is Type) for complex checks.
  • Prefer unions of string literals over enums for simple cases.

🎉 You have completed the TypeScript course! You now know the core features: types, interfaces, functions, classes, generics, modules, and union types. The next step is to build something real — a small project will teach you more than any tutorial.