← Back to Blog
9/12/20254 min read

TypeScript Union Types Explained

Master union types for flexible, type-safe code that handles multiple data shapes elegantly.

Basic Union Types

typescript
type Status = 'loading' | 'success' | 'error' type ID = string | number function formatId(id: ID): string { return id.toString() }

Discriminated Unions

The most powerful pattern - each variant has a common property:

typescript
interface LoadingState { status: 'loading' } interface SuccessState { status: 'success' data: any[] } interface ErrorState { status: 'error' error: string } type ApiState = LoadingState | SuccessState | ErrorState function handleApiState(state: ApiState) { switch (state.status) { case 'loading': return 'Loading...' case 'success': return `Loaded ${state.data.length} items` case 'error': return `Error: ${state.error}` } }

Real-World React Example

tsx
type UserPageState = | { type: 'idle' } | { type: 'loading' } | { type: 'success'; user: User } | { type: 'error'; message: string } function UserPage({ userId }: { userId: string }) { const [state, setState] = useState<UserPageState>({ type: 'idle' }) // TypeScript knows exactly what properties are available switch (state.type) { case 'loading': return <div>Loading user...</div> case 'success': return <h1>{state.user.name}</h1> // ✅ TypeScript knows user exists case 'error': return <div>Error: {state.message}</div> // ✅ message exists default: return <button onClick={loadUser}>Load User</button> } }

API Response Pattern

typescript
type ApiResponse<T> = | { success: true; data: T } | { success: false; error: string } async function fetchUser(id: string): Promise<ApiResponse<User>> { try { const data = await api.get(`/users/${id}`) return { success: true, data } } catch (error) { return { success: false, error: error.message } } } // Usage const result = await fetchUser('123') if (result.success) { console.log(result.data.name) // ✅ TypeScript knows data exists } else { console.error(result.error) // ✅ TypeScript knows error exists }

Best Practices

  1. Always use discriminated unions with a common property
  2. Use exhaustive checking to catch missing cases
  3. Keep variants simple - don't over-complicate the types
  4. Document your unions with clear comments

Union types make TypeScript incredibly expressive while keeping your code safe. Use them whenever you have data that can take multiple forms!