Union types and narrowing
A union type is A | B: the value can be A or B. You then narrow (check) which one it is with typeof or checks, so TypeScript knows the type in each branch.

Appy Says…
Sometimes a value can be one of several types — a loading state is either 'loading', 'success', or 'error'. TypeScript union types model this exactly, and with narrowing, you can handle each case safely.
What are Union Types?
A union type allows a value to be one of several types, separated by |. TypeScript tracks which possibilities are in play and narrows them as you check.
- •
type Status = 'loading' | 'success' | 'error'; - •
let result: string | number; - •Narrowing with
typeof:if (typeof x === 'string') { x.toUpperCase(); } - •Narrowing with
===:if (status === 'error') { showError(); } - •Discriminated union: objects with a shared
typefield - •Exhaustive check:
default: const _: never = status;— catches unhandled cases
Think of it like a Minecraft item that can be a sword OR a bow
An item slot can hold either a sword or a bow. Before using it, you check which it is. Union types work the same — a value could be string or number, and TypeScript requires you to check before using type-specific methods.
How It Works
- •1. Declare:
type ID = string | number; - •2. TS allows only methods common to all types unless you narrow
- •3. Narrow with typeof:
if (typeof id === 'string') { id.toLowerCase(); } - •4. Discriminated union:
type Shape = { kind: 'circle'; r: number } | { kind: 'rect'; w: number; h: number }; - •5. Narrow by discriminant:
if (shape.kind === 'circle') { /* r is available */ } - •6. Switch exhaustiveness:
default: const _exhaustive: never = shape;
Real-World Examples
- •API state:
type FetchState = { status: 'loading' } | { status: 'ok'; data: User } | { status: 'error'; message: string }; - •Form value:
type Value = string | number | boolean; - •React children:
type Children = React.ReactNode;(already a union) - •Redux actions: discriminated unions by
typefield
Key Facts
- •Discriminated unions with a
typeorkindfield are the most powerful TypeScript pattern - •TypeScript's control flow analysis tracks narrowing through if/switch/early return
- •
nevertype means 'this should never happen' — use in exhaustive switches - •Union types of literals (
'a' | 'b') are TypeScript's equivalent of enums for many use cases
Watch Out!
If you use a union type but forget to narrow before accessing type-specific methods, TypeScript errors. Don't cast with as string to silence the error — use a proper type guard (typeof or instanceof) to prove what type it actually is.
Remember
Union: A | B. Narrow before using type-specific members. Discriminated unions (shared kind field) are the cleanest pattern for state machines.
What You Learned
- •Union types:
A | B— value can be either type; narrow before using specific APIs - •Discriminated unions: shared kind field enables safe pattern matching
- •Unlocks: typed state machines, API states, flexible function parameters
Key Facts
- →Discriminated unions with a
typeorkindfield are the most powerful TypeScript pattern - →TypeScript's control flow analysis tracks narrowing through if/switch/early return
- →
nevertype means 'this should never happen' — use in exhaustive switches - →Union types of literals (
'a' | 'b') are TypeScript's equivalent of enums for many use cases
Real-World Examples
Remember
Union: A | B. Narrow before using type-specific members. Discriminated unions (shared kind field) are the cleanest pattern for state machines.
Quick Quiz
What does string | number mean?