BuildBot

Types in Motion

Generics & constraints

Lesson 2 of 3

What you'll learn

  • Write a generic function that preserves its input type
  • Add a constraint with extends to require certain shape
  • See why generics beat any for reusable code

A generic is a type variable — a placeholder a caller fills in. It lets one function work across types while keeping the connection between input and output:

function first<T>(items: T[]): T | undefined {
  return items[0];
}

const n = first([1, 2, 3]); // number | undefined
const s = first(["a", "b"]); // string | undefined

T is inferred from the argument, so first([1,2,3]) returns number | undefined, not any. The type flows through.

Constraints

Sometimes a generic needs a guarantee about its shape. extends constrains what T can be:

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

longest("hello", "hi"); // ok — strings have length
longest([1, 2], [3]); // ok — arrays have length
// longest(1, 2);       // error — numbers have no length

Generics vs. any

any switches off type checking; a generic keeps it on. With any, the return type is lost. With <T>, the caller still gets full autocomplete and safety on the result.

The challenge implements a generic pluck that reads one key from each object. Run it and notice the values keep their types.

A generic pluck

pluck reads one property from every item. Run it, then try plucking a different key.

Loading editor…

Next: the built-in utility types that transform other types for you.

Sign in to save your progress across devices.