Advanced TypeScript Patterns for Type Safety

Advanced TypeScript Patterns for Type Safety
TypeScript goes beyond basic type checking. These patterns help you build more robust, maintainable code.
Discriminated Unions
Combine discriminators with union types:
type Result<T> =
| { status: 'success'; data: T }
| { status: 'error'; error: string };
function handle<T>(result: Result<T>) {
if (result.status === 'success') {
console.log(result.data); // T is inferred
} else {
console.log(result.error); // string
}
}
Generic Constraints
Limit generic types for safer code:
interface HasId { id: number }
function getById<T extends HasId>(items: T[], id: number) {
return items.find(item => item.id === id);
}
Conditional Types
Type definitions based on conditions:
type Flatten<T> = T extends Array<infer U> ? U : T;
type Str = Flatten<string[]>; // string
type Num = Flatten<number>; // number
Utility Types
Built-in types for common patterns:
Exhaustiveness Checking
- `Partial<T>` - Make all properties optional
- `Required<T>` - Make all properties required
- `Readonly<T>` - Make all properties readonly
- `Pick<T, K>` - Select specific properties
- `Omit<T, K>` - Exclude specific properties
- `Record<K, T>` - Object with specific keys
Ensure all cases are handled:
type Status = 'pending' | 'success' | 'error';
function describe(status: Status): string {
switch(status) {
case 'pending': return 'Waiting...';
case 'success': return 'Done!';
case 'error': return 'Failed!';
default:
const _exhaustive: never = status;
return _exhaustive;
}
}
These patterns transform TypeScript from a type checker into a powerful development tool.
Key Takeaways
Frequently Asked Questions
What are discriminated unions in TypeScript?
- **Discriminated Unions**: Use a common property to create type-safe conditional logic for different variants.
- **Generic Constraints**: Enforce structure on generic types using `extends` to ensure they meet specific requirements.
- **Conditional Types**: Create dynamic type relationships that adapt based on the types provided.
- **Utility Types**: Leverage built-in helpers like `Pick`, `Omit`, and `Partial` to transform existing types efficiently.
- **Exhaustiveness Checking**: Use the `never` type to ensure all cases in a union are handled, preventing runtime errors.
Discriminated unions are a pattern where a common property (discriminator) is used to narrow down type in conditional logic, enabling type-safe handling of different variants.
When should I use generic constraints?
Use generic constraints when you need to limit what types can be passed to a generic function, ensuring the type has required properties or methods.
What are conditional types useful for?
Conditional types allow you to define type relationships based on conditions, enabling powerful type transformations and type-level programming.
How do utility types help in TypeScript?
Utility types like Partial, Required, Readonly, Pick, and Omit provide common type transformations that help you work with existing types more flexibly.
What is exhaustiveness checking?
Exhaustiveness checking ensures all possible cases are handled in switch statements or conditional logic, preventing runtime errors from unhandled cases.