Basic Union Types
typescripttype 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:
typescriptinterface 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
tsxtype 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
typescripttype 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
- Always use discriminated unions with a common property
- Use exhaustive checking to catch missing cases
- Keep variants simple - don't over-complicate the types
- 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!