TypeScript Generic Constraints
Limit generic types with constraints
🔗 What are Generic Constraints?
Generic constraints limit what types can be used with generics using the extends keyword. They ensure type parameters have specific properties or capabilities. This provides flexibility while maintaining type safety and preventing errors from missing properties or methods.
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(item: T): void {
console.log(item.length);
}
Constraint Types
Interface Constraints
Require specific properties
interface Named {
name: string;
}
function greet<T extends Named>(obj: T) {
console.log(obj.name);
}
Keyof Constraints
Limit to object keys
function getProperty<T, K extends keyof T>
(obj: T, key: K) {
return obj[key];
}
Class Constraints
Require constructor types
function create<T>(c: new () => T): T {
return new c();
}
Multiple Constraints
Combine multiple requirements
function process<T extends Named & HasId>
(item: T) {
// item has name and id
}
🔹 Basic Constraints
Constrain generics to types with specific properties:
// Interface defining the constraint
interface HasLength {
length: number;
}
// Function that requires length property
function logLength<T extends HasLength>(item: T): void {
console.log(`Length: ${item.length}`);
}
// These work - they have length property
logLength("hello"); // string has length
logLength([1, 2, 3]); // array has length
logLength({ length: 10 }); // object with length
// This would error - number doesn't have length
// logLength(42); // ❌ Error
Output:
Length: 5 Length: 3 Length: 10
🔹 Keyof Constraints
Ensure a type parameter is a key of another type:
// Get property value safely
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const person = {
name: "Alice",
age: 30,
city: "NYC"
};
console.log(getProperty(person, "name")); // Alice
console.log(getProperty(person, "age")); // 30
console.log(getProperty(person, "city")); // NYC
// This would error - "salary" is not a key of person
// console.log(getProperty(person, "salary")); // ❌ Error
Output:
Alice 30 NYC
🔹 Interface Constraints
Require objects to implement specific interfaces:
interface Identifiable {
id: number;
}
interface Named {
name: string;
}
// Function requiring both interfaces
function displayItem<T extends Identifiable & Named>(item: T): void {
console.log(`ID: ${item.id}, Name: ${item.name}`);
}
const user = { id: 1, name: "John", email: "[email protected]" };
const product = { id: 101, name: "Laptop", price: 999 };
displayItem(user); // ID: 1, Name: John
displayItem(product); // ID: 101, Name: Laptop
// This would error - missing required properties
// displayItem({ id: 1 }); // ❌ Error: missing name
// displayItem({ name: "Test" }); // ❌ Error: missing id
Output:
ID: 1, Name: John ID: 101, Name: Laptop
🔹 Class Type Constraints
Work with constructor functions and create instances:
// Base class
class Animal {
constructor(public name: string) {}
makeSound(): void {
console.log("Some sound");
}
}
class Dog extends Animal {
makeSound(): void {
console.log(`${this.name} barks: Woof!`);
}
}
class Cat extends Animal {
makeSound(): void {
console.log(`${this.name} meows: Meow!`);
}
}
// Function that creates instances of Animal subclasses
function createAnimal<T extends Animal>(
AnimalClass: new (name: string) => T,
name: string
): T {
return new AnimalClass(name);
}
const dog = createAnimal(Dog, "Buddy");
const cat = createAnimal(Cat, "Whiskers");
dog.makeSound(); // Buddy barks: Woof!
cat.makeSound(); // Whiskers meows: Meow!
Output:
Buddy barks: Woof! Whiskers meows: Meow!
🔹 Comparing Objects
Use constraints to safely compare objects:
interface Comparable {
compareTo(other: Comparable): number;
}
class Person implements Comparable {
constructor(public name: string, public age: number) {}
compareTo(other: Person): number {
return this.age - other.age;
}
}
function findMin<T extends Comparable>(items: T[]): T | undefined {
if (items.length === 0) return undefined;
let min = items[0];
for (let i = 1; i < items.length; i++) {
if (items[i].compareTo(min as any) < 0) {
min = items[i];
}
}
return min;
}
const people = [
new Person("Alice", 30),
new Person("Bob", 25),
new Person("Charlie", 35)
];
const youngest = findMin(people);
console.log(`Youngest: ${youngest?.name}, Age: ${youngest?.age}`);
Output:
Youngest: Bob, Age: 25
🔹 Array Operations with Constraints
Create type-safe array utilities:
interface HasId {
id: number;
}
// Find item by ID
function findById<T extends HasId>(items: T[], id: number): T | undefined {
return items.find(item => item.id === id);
}
// Remove item by ID
function removeById<T extends HasId>(items: T[], id: number): T[] {
return items.filter(item => item.id !== id);
}
const users = [
{ id: 1, name: "Alice", role: "admin" },
{ id: 2, name: "Bob", role: "user" },
{ id: 3, name: "Charlie", role: "user" }
];
const user = findById(users, 2);
console.log(user); // { id: 2, name: 'Bob', role: 'user' }
const remaining = removeById(users, 2);
console.log(remaining.length); // 2
console.log(remaining.map(u => u.name)); // ['Alice', 'Charlie']
Output:
{ id: 2, name: 'Bob', role: 'user' }
2
['Alice', 'Charlie']
💡 Key Points:
-
Use
extendskeyword to add constraints:<T extends Type> -
keyofconstraint ensures type is a valid key:<K extends keyof T> -
Combine multiple constraints with
&:<T extends A & B> -
Constructor constraints:
new () => T - Constraints provide type safety while maintaining flexibility
- Use constraints to access properties safely in generic code