Without a constraint, a generic type parameter T could be absolutely anything, so TypeScript only allows operations valid for every conceivable type — which in practice means almost nothing. Writing T extends SomeType restricts T to types assignable to SomeType, letting the function safely use SomeType's members on any value of type T. A common and useful constraint is keyof another generic, restricting one parameter's valid values to the property names of another.
The problem an unconstrained generic has
TypeScript has no idea T even has a .length property — T could be a number, a boolean, anything.
function logLength<T>(item: T) {
// console.log(item.length); // COMPILE ERROR: T might not have a length property
}Adding a constraint with extends
Now T is restricted to any type with a .length property, so accessing it is safe — while T can still be a string, an array, or any custom type that happens to have that shape.
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(item: T) {
console.log(item.length); // safe: T is guaranteed to have .length
}
logLength("hello"); // fine — string has .length
logLength([1, 2, 3]); // fine — array has .length
// logLength(42); // COMPILE ERROR: number doesn't have .lengthkeyof constraints: restricting to another type's keys
This is exactly how TypeScript's built-in Pick<T, K> and similar utility types constrain their key parameter — preventing a caller from passing a property name that doesn't actually exist on the object.
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"); // fine, returns string
// getProperty(user, "email"); // COMPILE ERROR: "email" is not a key of user