TypeScript Type Guards with 'is' Keyword - Narrowing Union Types Safely
Problem
When an API response comes as a union of success and error types, TypeScript can’t narrow the type inside if blocks without explicit type assertions.
type SuccessResponse = { status: 'ok'; data: string[] };
type ErrorResponse = { status: 'error'; message: string };
type ApiResponse = SuccessResponse | ErrorResponse;
function handle(res: ApiResponse) {
// Can't access res.data - type isn't narrowed
}
Solution
Create a custom type guard function using the is keyword.
function isSuccess(res: ApiResponse): res is SuccessResponse {
return res.status === 'ok';
}
function handle(res: ApiResponse) {
if (isSuccess(res)) {
// res is narrowed to SuccessResponse here
console.log(res.data);
} else {
// res is ErrorResponse here
console.log(res.message);
}
}
This is also useful for array filtering.
const results: (string | null)[] = ['a', null, 'b', null];
// Still (string | null)[] after filter...
const bad = results.filter(x => x !== null);
// Narrowed to string[] with type guard
const good = results.filter((x): x is string => x !== null);
Key Points
- The
iskeyword goes in the return type position — when the function returnstrue, TypeScript narrows the type accordingly - Passing a type guard to
Array.filterautomatically narrows the resulting array type - Most useful for complex type branching where
typeoforinoperators fall short