📚 5 min read
Type Guards in TypeScript ​
This section covers TypeScript type guards and their practical applications in type narrowing.
Overview ​
Type guards are expressions that perform runtime checks to guarantee the type of a value in a given scope. They help TypeScript narrow down the type of a variable within conditional blocks.
Basic Type Guards ​
typeof Type Guard ​
typescript
function processValue(value: string | number) {
if (typeof value === 'string') {
// TypeScript knows value is a string here
return value.toLowerCase();
} else {
// TypeScript knows value is a number here
return value.toFixed(2);
}
}
instanceof Type Guard ​
typescript
class ApiError extends Error {
statusCode: number;
constructor(message: string, statusCode: number) {
super(message);
this.statusCode = statusCode;
}
}
class ValidationError extends Error {
fieldErrors: Record<string, string[]>;
constructor(message: string, fieldErrors: Record<string, string[]>) {
super(message);
this.fieldErrors = fieldErrors;
}
}
function handleError(error: Error) {
if (error instanceof ApiError) {
// TypeScript knows error is ApiError
console.error(`API Error ${error.statusCode}: ${error.message}`);
} else if (error instanceof ValidationError) {
// TypeScript knows error is ValidationError
console.error('Validation Error:', error.fieldErrors);
} else {
// TypeScript knows error is Error
console.error('Generic Error:', error.message);
}
}
Custom Type Guards ​
User-Defined Type Guards ​
typescript
interface User {
id: string;
name: string;
email: string;
}
interface Admin extends User {
role: 'admin';
permissions: string[];
}
function isAdmin(user: User): user is Admin {
return 'role' in user && user.role === 'admin';
}
function handleUser(user: User) {
if (isAdmin(user)) {
// TypeScript knows user is Admin
console.log(`Admin ${user.name} has permissions:`, user.permissions);
} else {
// TypeScript knows user is just User
console.log(`User ${user.name}`);
}
}
Array Type Guards ​
typescript
function isNonEmpty<T>(arr: T[]): arr is [T, ...T[]] {
return arr.length > 0;
}
function isArrayOfStrings(arr: unknown[]): arr is string[] {
return arr.every((item): item is string => typeof item === 'string');
}
function processItems<T>(items: T[]) {
if (isNonEmpty(items)) {
// TypeScript knows items has at least one element
const [first, ...rest] = items;
console.log('First item:', first);
}
if (isArrayOfStrings(items)) {
// TypeScript knows items is string[]
items.map((str) => str.toLowerCase());
}
}
Advanced Patterns ​
Discriminated Unions ​
typescript
type Result<T> =
| { type: 'success'; data: T }
| { type: 'error'; error: Error }
| { type: 'loading' };
function handleResult<T>(result: Result<T>) {
switch (result.type) {
case 'success':
// TypeScript knows result has data
console.log('Success:', result.data);
break;
case 'error':
// TypeScript knows result has error
console.error('Error:', result.error.message);
break;
case 'loading':
// TypeScript knows result has no additional properties
console.log('Loading...');
break;
}
}
Assertion Functions ​
typescript
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== 'string') {
throw new Error('Value must be a string');
}
}
function assertNonNull<T>(value: T | null | undefined): asserts value is T {
if (value === null || value === undefined) {
throw new Error('Value must not be null or undefined');
}
}
function processData(data: unknown) {
assertIsString(data);
// TypeScript knows data is string
console.log(data.toLowerCase());
}
function getUserName(user: { name: string } | null) {
assertNonNull(user);
// TypeScript knows user is not null
return user.name;
}
Real-World Example ​
typescript
// API Response types
type ApiResponse<T> =
| { status: 'success'; data: T }
| { status: 'error'; error: string }
| { status: 'loading' };
// Domain types
interface Product {
id: string;
name: string;
price: number;
}
interface Order {
id: string;
items: Product[];
total: number;
}
// Type guards
function isProduct(value: unknown): value is Product {
return (
typeof value === 'object' &&
value !== null &&
'id' in value &&
'name' in value &&
'price' in value &&
typeof (value as Product).id === 'string' &&
typeof (value as Product).name === 'string' &&
typeof (value as Product).price === 'number'
);
}
function isOrder(value: unknown): value is Order {
return (
typeof value === 'object' &&
value !== null &&
'id' in value &&
'items' in value &&
'total' in value &&
typeof (value as Order).id === 'string' &&
Array.isArray((value as Order).items) &&
(value as Order).items.every(isProduct) &&
typeof (value as Order).total === 'number'
);
}
// API handler with type guards
class ApiHandler {
async fetchOrder(orderId: string): Promise<ApiResponse<Order>> {
try {
const response = await fetch(`/api/orders/${orderId}`);
const data = await response.json();
if (!isOrder(data)) {
return {
status: 'error',
error: 'Invalid order data received',
};
}
return {
status: 'success',
data,
};
} catch (error) {
return {
status: 'error',
error: error instanceof Error ? error.message : 'Unknown error',
};
}
}
processApiResponse<T>(
response: ApiResponse<T>,
handlers: {
onSuccess: (data: T) => void;
onError: (error: string) => void;
onLoading: () => void;
}
) {
switch (response.status) {
case 'success':
handlers.onSuccess(response.data);
break;
case 'error':
handlers.onError(response.error);
break;
case 'loading':
handlers.onLoading();
break;
}
}
}
// Usage
const api = new ApiHandler();
async function displayOrder(orderId: string) {
const response = await api.fetchOrder(orderId);
api.processApiResponse(response, {
onSuccess: (order) => {
console.log('Order details:', {
id: order.id,
itemCount: order.items.length,
total: order.total,
});
},
onError: (error) => {
console.error('Failed to fetch order:', error);
},
onLoading: () => {
console.log('Loading order...');
},
});
}
Best Practices ​
Type Guard Design:
- Keep type guards simple and focused
- Use descriptive names that indicate the check
- Return boolean for type predicates
Error Handling:
- Use assertion functions for invariants
- Provide descriptive error messages
- Handle edge cases explicitly
Performance:
- Avoid excessive type checking
- Use discriminated unions when possible
- Cache type guard results when appropriate