Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Union and Intersection Types in TypeScript

Union and Intersection Types

~15 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026

Chapter 8 already used BookStatus = 'available' | 'borrowed' | 'reserved' – time to understand the | symbol (union) and its counterpart & (intersection) in detail.

Union types: "either – or"

type IdentificationNumber = string | number;

function findBookById(id: IdentificationNumber): void {
  console.log(`Searching for ID: ${id}`);
}

findBookById('978-0-618-64015-7'); // ✓ string
findBookById(42);                  // ✓ number
findBookById(true);                // ✗ error - boolean is NOT part of the union

A union type with | means: the value is ONE of the listed types – NOT both at once. IdentificationNumber accepts string OR number, but NO third type.

Inside a union: only SHARED properties are usable

type SearchResult = Book | Author;

function showResult(result: SearchResult): void {
  console.log(result.name); // error - 'name' doesn't exist on 'Book'
  // TypeScript does NOT know whether 'result' is currently a Book or an Author,
  // so only properties SHARED by BOTH types are allowed
}

Achtung: A common beginner mistake: inside a function accepting a union type, ONLY the properties ALL involved types have IN COMMON can be used directly. To use type-specific properties, you need "type narrowing" (chapter 19) – there, we'll solve EXACTLY this problem.

Intersection types: "both – and"

interface HasId {
  id: string;
}

interface HasTimestamp {
  createdAt: Date;
}

type Entity = HasId & HasTimestamp;

const example: Entity = {
  id: 'abc-123',
  createdAt: new Date(),
}; // MUST have BOTH properties - id AND createdAt

& (intersection) means the EXACT opposite of |: the resulting type has ALL properties of ALL involved types AT ONCE. Entity needs both id AND createdAt.

In practice: modeling a well-designed search result

Our library needs to search for both books AND authors – a classic case for a union type with a DISTINGUISHABLE field (a "discriminated union"):

src/models/SearchResult.ts
import { Book } from './Book.js';
import { Author } from './Author.js';

export interface BookMatch {
  type: 'book'; // literal type - ALWAYS exactly this one string value
  data: Book;
}

export interface AuthorMatch {
  type: 'author';
  data: Author;
}

export type SearchResult = BookMatch | AuthorMatch;
function formatMatch(match: SearchResult): string {
  if (match.type === 'book') {
    // TypeScript knows HERE: match is a BookMatch, match.data is a Book
    return `Book: ${match.data.title}`;
  }
  // TypeScript knows HERE: match MUST be an AuthorMatch (process of elimination)
  return `Author: ${match.data.name}`;
}

The shared type field with DIFFERENT literal values ('book' vs. 'author') is the key to a "discriminated union" – the if (match.type === 'book') check automatically NARROWS the type ("narrowing", covered in detail in chapter 19), without us having to do it explicitly ourselves. This pattern is one of the most useful and most commonly used TypeScript techniques there is.

Tipp: Rule of thumb: once a value can be "this OR that" AND you'll later need to know WHICH of the two it is, add a shared "discriminator" field (type, kind) with a UNIQUE literal value each – that makes later if/switch checks both more readable and more type-safe.