Technology · TypeScript
TypeScript Generics Constraints
Build flexible but safe types using constrained generics.
TL;DR
- 01Use extends to constrain generic types to specific shapes.
- 02Use keyof to safely access object properties.
- 03Combine constraints with conditional types for powerful patterns.
Basic Constraints
- Constrain generic type to a specific type with extends.
function getLength<T extends { length: number }>(item: T): number { return item.length; } getLength("hello"); // works: string has length getLength([1, 2, 3]); // works: array has length getLength(123); // error: number has no length - Constrain to primitive types.
function identity<T extends string | number>(value: T): T { return value; } identity("text"); // works identity(42); // works identity({}); // error - Extend specific classes or interfaces.
interface HasId { id: string; } function getId<T extends HasId>(item: T): string { return item.id; }
Keyof Constraints
- Use keyof to safely access object properties.
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } const user = { name: "Alice", age: 30 }; getProperty(user, "name"); // works: "name" is a key getProperty(user, "email"); // error: "email" is not a key - Create flexible getters with key constraints.
function getProp<T extends object, K extends keyof T>( obj: T, key: K ): T[K] { return obj[key]; }
Multiple Constraints
- Combine constraints with intersection types.
interface HasId { id: string; } interface HasName { name: string; } function process<T extends HasId & HasName>(item: T): string { return `${item.id}: ${item.name}`; } - Constrain multiple generic parameters.
function merge<T extends object, U extends object>( obj1: T, obj2: U ): T & U { return { ...obj1, ...obj2 }; } - Use conditional constraints for complex logic.
function getValue<T extends string | number | boolean>( value: T ): T extends string ? string : T extends number ? number : boolean { return value; }
Constructor Constraints
- Constrain generic to a class constructor.
function create<T extends { new (...args: any[]): {} }>( constructor: T ): InstanceType<T> { return new constructor(); } class User { } const user = create(User); // user is typed as User - Accept class instances with type preservation.
function instantiate<T extends new () => any>( Class: T ): InstanceType<T> { return new Class(); }
Practical Patterns
- Create type-safe array utilities with constraints.
function findById<T extends { id: string | number }>( items: T[], id: string | number ): T | undefined { return items.find(item => item.id === id); } - Build flexible mappers with key constraints.
function mapProperty<T, K extends keyof T>( items: T[], key: K ): T[K][] { return items.map(item => item[key]); } const names = mapProperty(users, "name"); - Create type-safe event handlers.
type EventMap = { login: { userId: string }; logout: { timestamp: number }; }; function on<E extends keyof EventMap>( event: E, handler: (data: EventMap[E]) => void ) { // Type-safe handler }
Tip: Use constraints to catch errors at compile time instead of runtime — this prevents many subtle bugs in generic code.
Warning: Over-constraining generics can make types too restrictive — find the right balance between flexibility and safety.