A generic constraint limits what a type parameter can be. <T extends { length: number }> accepts only types that have a numeric length (strings, arrays, your own objects), and because of that promise the function is allowed to read .length.
The return type is still T, not { length: number }: passing two arrays of numbers gives back number[], and the objects keep their tag. The constraint describes the minimum; the caller's type is preserved.
Why Constraints Are Needed
Inside a generic function, an unconstrained T could be anything: a string, a number, null, a function. So TypeScript only allows what works on every type, which is almost nothing.
The compiler prints:
index.ts(3,12): error TS2339: Property 'length' does not exist on type 'T'.
index.ts(3,24): error TS2339: Property 'length' does not exist on type 'T'.
The fix is to say what you need: <T extends { length: number }>. Constraints turn "any type" into "any type that has this", which is usually what a generic function means.
Constraining to an Interface
The constraint can be any type, including an interface or type alias you already have. The function then works with anything that has at least those members, and still returns the caller's full type.
Compare with a plain parameter type: function byId(items: HasId[], id: number): HasId | undefined accepts the same arrays, but the result is only HasId, so found.name would be an error. The generic version passes the full type through.
K extends keyof T: Safe Property Access
The most common constraint relates two type parameters. K extends keyof T says K must be one of T's property names, and T[K] is the type of that property.
A misspelled or missing key is a compile error, and the return type follows the key: "year" gives number, "title" gives string. The keyof page covers keyof itself, including how it behaves with index signatures.
Constraining to Primitives
A constraint can be a primitive or a union of them. A useful side effect: when T extends string (or number), TypeScript infers the literal type of the argument instead of widening it.
Without the constraint, createEvent("user.saved") returns { type: string }: here TypeScript widens the literal it inferred for T to string. With T extends string the literal is kept, which is how typed event and routing helpers get exact names out of plain string arguments.
Common Errors
Returning something that only matches the constraint. A function that returns T must return a T, not just any value that fits the constraint:
interface HasId {
id: number;
}
function reset<T extends HasId>(item: T): T {
return { id: item.id };
}
// error TS2322: Type '{ id: number; }' is not assignable to type 'T'.
// '{ id: number; }' is assignable to the constraint of type 'T', but 'T' could be
// instantiated with a different subtype of constraint 'HasId'.
If T is { id: number; name: string }, the new object has no name, so it is not a T. Return a spread of the input, return { ...item, id: 0 };, which keeps every property, or declare the return type as HasId if that is all you produce.
Passing a type argument that breaks the constraint. Explicit type arguments are checked too: with function scale<T extends number>(x: T), the call scale<string>("2") fails with Type 'string' does not satisfy the constraint 'number'. (TS2344).
Constraining when you do not need a generic. If T appears only in the parameter, function print<T extends HasId>(item: T): void is just function print(item: HasId): void with extra syntax. Constraints matter when T also appears in the return type or in another parameter.
Constraints With Defaults
A type parameter can have both a constraint and a default. The default must satisfy the constraint.
interface Store<TState extends object = Record<string, unknown>> {
get(): TState;
set(next: Partial<TState>): void;
}
type AnyStore = Store; // Store<Record<string, unknown>>
type CounterStore = Store<{ count: number }>; // fine
type BadStore = Store<number>;
// error TS2344: Type 'number' does not satisfy the constraint 'object'.
The same extends keyword also appears in conditional types, T extends string ? A : B, where it is a test rather than a restriction; the conditional types page covers that form.
Frequently Asked Questions
What does T extends mean in a TypeScript generic?
<T extends Constraint> means T can be any type that is assignable to Constraint. Callers can only pass such types, and inside the function you may use everything the constraint guarantees. <T extends { length: number }> accepts strings, arrays and any object with a numeric length, and lets you read value.length.
What does K extends keyof T mean?
K must be one of the property names of T. Together with the indexed access type T[K] it types property access exactly: function get<T, K extends keyof T>(obj: T, key: K): T[K] only accepts real keys of obj, and the return type is the type of that property.
Why do I get "Property does not exist on type T"?
An unconstrained T could be any type, including number or null, so TypeScript allows nothing type-specific on it (TS2339). Add a constraint that promises the property: <T extends { name: string }>, or constrain to an interface you already have, <T extends User>.
What does "T could be instantiated with a different subtype of constraint" mean?
Error TS2322: the function promises to return T, but returns a value that only matches the constraint. If T is { id: number; name: string }, a new { id: 1 } is not a T. Return the value you received (or a spread of it, typed accordingly), or change the return type to the constraint.
What is the difference between extends in a generic and extends in a class or interface?
Same keyword, related idea. In class Dog extends Animal and interface B extends A it declares inheritance. In <T extends A> it declares a constraint: T must be assignable to A. In a conditional type, T extends A ? X : Y, it is a test that picks one of two types.