Guide
TypeScript Discriminated Unions for Type-Safe API Handling
Discriminated unions use a shared literal property to let TypeScript automatically narrow types, eliminating unsafe casts when handling multiple API response shapes.
Published by Tasadduq Burney
12 Sept 2025, 02:30 UTC
1 min57.6K views0

Using Discriminated Unions for Type-Safe API Responses
When processing API responses that can succeed, fail, or load, developers often resort to runtime checks or unsafe type assertions. A discriminated union introduces a shared literal property the discriminant that lets TypeScript automatically narrow types within conditional blocks.
Defining the Union Types
type Success = { status: 'success'; data: string[] };
type Error = { status: 'error'; message: string; code: number };
type Loading = { status: 'loading' };
type ApiResponse = Success | Error | Loading;
function handle(response: ApiResponse) {
switch (response.status) {
case 'success':
return response.data;
case 'error':
throw new Error(response.message);
case 'loading':
return undefined;
}
}Enforcing Exhaustiveness
To catch missing cases when the union is extended, type the default switch branch as never:
function exhaustive(response: ApiResponse) {
switch (response.status) {
case 'success':
return response.data;
case 'error':
return response.message;
case 'loading':
return undefined;
default:
const _check: never = response;
return _check; // Compile error if new member added
}
}Limitations and Common Mistakes
- The discriminant must be a literal type; a plain string property prevents type narrowing.
- Deeply nested or wide unions can slow type-checking and IDE responsiveness.
- Omitting a case when adding a new union member causes runtime errors unless exhaustiveness is enforced.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.