This site uses tracking technologies. You may opt in or opt out of the use of these technologies.

Back to Typescript

Typescript Generic Constraints: Apply constraints to generics for more precise type definitions.

In TypeScript, generic constraints allow you to enforce certain rules on generic types, making your type definitions more precise and your code safer.

Constraints are specified using the extends keyword, which restricts what types can be used as generic arguments.

Here are some examples of how to use generic constraints in TypeScript:

1. Basic Constraints with extends

You can constrain a generic type to extend a specific type, ensuring that only types compatible with that constraint are accepted.

// A generic function that only accepts objects with a `name` property
function printName<T extends { name: string }>(obj: T): void {
console.log(obj.name);
}
printName({ name: "Alice" }); // ✅ Works
printName({ name: "Bob", age: 25 }); // ✅ Works
// printName({ age: 25 }); // ❌ Error: Property 'name' is missing

In this example, T is constrained to types that have a name property of type string.

2. Constraining to a Union of Types

You can restrict a generic to a specific set of types by using union types in the constraint.

function getLength<T extends string | any[]>(input: T): number {
return input.length;
}
getLength("hello"); // ✅ Works, returns 5
getLength([1, 2, 3]); // ✅ Works, returns 3
// getLength(123); // ❌ Error: Type 'number' is not assignable to type 'string | any[]'

Here, T can only be a string or an array (any[]), both of which have a length property.

3. Constraining to Class Instances

You can use classes as constraints to ensure the generic type is an instance of a particular class.

class Animal {
sound() {
console.log("Some sound");
}
}
class Dog extends Animal {
bark() {
console.log("Woof!");
}
}
function makeSound<T extends Animal>(animal: T): void {
animal.sound();
}
makeSound(new Dog()); // ✅ Works
// makeSound({} as string); // ❌ Error: Type 'string' is not assignable to type 'Animal'

In this example, T is constrained to be of type Animal or any subclass of Animal, so you can call sound() on animal.

4. Multiple Constraints with &

You can apply multiple constraints by combining them with &. This requires the generic type to satisfy all specified constraints.

interface Identifiable {
id: number;
}
interface Nameable {
name: string;
}
function printPersonInfo<T extends Identifiable & Nameable>(person: T): void {
console.log(`ID: ${person.id}, Name: ${person.name}`);
}
printPersonInfo({ id: 1, name: "Alice" }); // ✅ Works
// printPersonInfo({ id: 1 }); // ❌ Error: Property 'name' is missing
// printPersonInfo({ name: "Alice" }); // ❌ Error: Property 'id' is missing

Here, T is constrained to types that have both id and name properties.

5. Using keyof to Constrain to Keys of a Type

You can use keyof to constrain a generic type to be one of the keys of another type.

type Person = {
id: number;
name: string;
age: number;
};
function getProperty<T extends keyof Person>(key: T): string {
return `Property name: ${key}`;
}
getProperty("name"); // ✅ Works
getProperty("age"); // ✅ Works
// getProperty("address"); // ❌ Error: Type '"address"' is not assignable to parameter of type 'keyof Person'

Here, T is constrained to be one of the keys of the Person type (id, name, or age), so you can't pass a key that doesn't exist on Person.

6. Default Values with Constraints

You can set a default type with a constraint to avoid having to specify the type in every call.

function createEntity<T extends { id: number } = { id: number }>(entity: T): T {
console.log(entity.id);
return entity;
}
createEntity({ id: 1, name: "Alice" }); // ✅ Works with inferred type
createEntity<{ id: number; age: number }>({ id: 2, age: 30 }); // ✅ Works with explicit type

In this example, if no type argument is provided, T defaults to { id: number }.