extends (generic constraint)
Restrict a type parameter to types that match a shape, so its members are usable inside the body.
function loggingIdentity<Type extends Lengthwise>(arg: Type): Type
function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key)Parameters
| Parameter | Type | Description |
|---|---|---|
$Type | type parameter | The type parameter being constrained. Inside the body it is treated as its constraint, so only members of the constraint are accessible. |
$Lengthwise | constraint type | The upper bound written after `extends`. Any type argument must be assignable to it; common bounds are `object`, `keyof Type`, `readonly unknown[]`, `string` and interfaces such as `{ length: number }`. |
Return value
Unchanged — a constraint narrows what may be passed and what is visible inside the body; it does not alter the return type.
Errors
Nothing at runtime. tsc reports error 2344, "Type 'X' does not satisfy the constraint 'Y'", for an explicit type argument, or error 2345 when an argument fails the constraint through inference.
Changelog
- PHP 2.9`keyof` on a type with number or symbol keys includes those keys, so `K extends keyof T` may be a union of string, number and symbol rather than string alone.
- PHP 3.0`unknown` became available as the safe default upper bound, replacing the habit of constraining to `{}` or leaving the parameter open as `any`.
extends in a type parameter list sets an upper bound. <Type extends Lengthwise> says every type argument must be assignable to Lengthwise, and in exchange the body may read arg.length. Without the constraint that property access is an error, because an open type parameter could be anything.
The two constraints you will actually write
Key extends keyof Type— the key must be one thatTypereally has.getProperty(x, "m")on{ a, b, c, d }fails with "Argument of type '"m"' is not assignable to parameter of type '"a" | "b" | "c" | "d"'".Type extends objector a structural bound such as{ length: number }— enough to use the members you need without naming a concrete type.
Not the same as conditional-type extends
The same keyword does two different jobs. In a parameter list it restricts; in T extends U ? X : Y it tests assignability and picks a branch. A constraint rejects bad arguments at the call site; a conditional type accepts everything and reshapes the result.
What trips people up
Inside the body the parameter behaves like its constraint, not like the eventual argument. You cannot return a fresh object literal as Type, because a caller may have passed a subtype with extra required members.
Example
When to use it
- Type a getProperty or pluck helper so only real keys are accepted
- Require a length or id member before reading it inside a generic function
- Restrict a factory to constructors with new () => T
- Bound a tuple helper with T extends readonly unknown[] so spreads type-check
Frequently asked questions
What is the difference between extends in a generic constraint and extends in a conditional type?
In a type parameter list extends sets an upper bound and rejects arguments that do not match; in T extends U ? X : Y it is an assignability test that chooses a branch and never rejects anything. The first is a gate at the call site, the second is a computation on the type.
How do I make a generic function accept only real property names?
Constrain a second parameter with keyof: function getProperty<T, K extends keyof T>(obj: T, key: K): T[K]. K is then inferred as the literal key passed in, the return type is that property's type, and an unknown key fails with error 2345.
Why can't I return an object literal typed as my constrained parameter T?
Because a constraint is an upper bound: the caller may pass a subtype with extra required members, so an object with only the constraint's properties is not a valid T. Return the constraint type, take the object as a parameter and modify it, or use a cast if you are certain.
More examples
Constraint versus conditional type
firstKey(42) would be rejected outright because number is not assignable to object, while ElementOf<number> happily resolves to number by taking the false branch. Output is "mode" then the string and 7 — Object.keys is sorted so the first key is deterministic regardless of insertion order.
// extends as a CONSTRAINT: restricts what may be passed
function firstKey<T extends object>(obj: T): keyof T {
return Object.keys(obj).sort()[0] as keyof T;
}
// extends as a CONDITIONAL TEST: accepts anything, picks a branch
type ElementOf<T> = T extends readonly (infer E)[] ? E : T;
const config = { retries: 3, mode: "fast" };
console.log(firstKey(config));
const sample: ElementOf<string[]> = "only a string survives the unwrap";
const plain: ElementOf<number> = 7;
console.log(sample, plain);Constraining to a constructor
The new () => A parameter type means createInstance takes the class itself rather than an instance, and A extends Animal keeps out unrelated constructors. Because A is inferred as Bee, bee.makesHoney is visible without a cast; output is 6 true 4. This is the pattern the handbook uses for mixins.
class Animal {
numLegs = 4;
}
class Bee extends Animal {
numLegs = 6;
makesHoney = true;
}
function createInstance<A extends Animal>(c: new () => A): A {
return new c();
}
const bee = createInstance(Bee);
const animal = createInstance(Animal);
console.log(bee.numLegs, bee.makesHoney, animal.numLegs);
Discussion