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
extendsto require certain shape - See why generics beat
anyfor 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.
pluck reads one property from every item. Run it, then try plucking a different key.
Next: the built-in utility types that transform other types for you.
Sign in to save your progress across devices.