Generic constraints (extends)

Requiring a type parameter to match a specific shape.

On this page

Example

#
TypeScript
function longest<T extends { length: number }>(a: T, b: T): T {
  return a.length >= b.length ? a : b
}

console.log(longest('HTML', 'JavaScript'))
console.log(longest([1, 2, 3], [4, 5]))
Result
JavaScript
[1, 2, 3]

Definition and usage

#

A type parameter with no constraint can be anything, so inside the function you can’t assume it has any particular field. The notation <T extends { length: number }> says: “T can be any type, but it must have a length field.”

Thanks to constraints, a generic function can safely use the parameter’s properties, and the compiler rejects calls with types that don’t have them.

A very common combination is with keyof: <T, K extends keyof T> guarantees that the second argument is one of the first object’s keys.

Syntax

#
Syntax
function name<T extends Condition>(param: T) { … }

More examples

#
A key that definitely exists
TypeScript
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
  return items.map((item) => item[key])
}

const students = [
  { name: 'Emma', level: 7 },
  { name: 'Jack', level: 3 }
]

console.log(pluck(students, 'name'))
console.log(pluck(students, 'level'))
Result
['Emma', 'Jack']
[7, 3]
A call rejected by the constraint
TypeScript
longest(10, 20)
// Error: Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.

Good practices

#
  • Constrain type parameters only as much as the function’s code actually requires. Too narrow a constraint reduces the function’s usefulness.
  • The pattern K extends keyof T is the foundation of safe functions that work on object keys.

Related entries

#

Spotted a mistake or missing an example? Write to us.