How to constrain a generic type.
Tell the compiler what T can do.
You write function getLength and get an error: Property 'length' does not exist on type 'T'. Of course it doesn't — T could be a number. The fix is a constraint: tell the compiler that T has at least a length property. This is the pattern behind every typed helper you'll write — and behind the keyof pattern that powers every typed getter.
Short answer: Add extends to the type parameter: . The constraint tells the compiler "T has at least these properties," which unlocks accessing them inside the function. The pattern for typed property access: . Constraints can be object shapes, unions, or other type parameters.
✅ Solution 1 — Constrain to an object shape
When you need to access a property on T.
The most common constraint: "T must have this property." Without it, accessing x.length, x.id, or any specific property on a bare T fails. The constraint describes the minimum requirement — T can be anything that satisfies it, and you get access to the required fields.
// ─── The problem: unconstrained T ──────────── function badLength(x: T): number { // return x.length; // ❌ Property 'length' does not exist on type 'T'. // T could be a number, an object, anything — no guarantee // it has 'length'. return 0; } // ─── The fix: constrain T ──────────────────── function getLength extends { length: number }>(x: T): number { return x.length; // ✅ T guaranteed to have length: number } getLength('hello'); // ✅ string has length getLength([1, 2, 3]); // ✅ array has length getLength({ length: 5 }); // ✅ any object with length // getLength(42); // ❌ number doesn't have length // getLength({ name: 'x' }); // ❌ no length property // ─── Constrain to a named interface ────────── interface HasId { id: string; } function findById extends HasId>(items: T[], id: string): T | undefined { return items.find(item => item.id === id); } interface User { id: string; name: string } interface Post { id: string; title: string } const users: User[] = [{ id: '1', name: 'Alice' }]; const posts: Post[] = [{ id: 'p1', title: 'Hello' }]; findById(users, '1'); // ✅ returns User | undefined findById(posts, 'p1'); // ✅ returns Post | undefined // ─── Multiple constraints via & ────────────── interface HasName { name: string } function greetById extends HasId & HasName>(item: T): string { return ${item.id}: ${item.name}; } console.log(getLength('hello'), findById(users, '1'));
function badLength(x) { return 0; } function getLength(x) { return x.length; } getLength('hello'); getLength([1, 2, 3]); getLength({ length: 5 }); function findById(items, id) { return items.find(item => item.id === id); } const users = [{ id: '1', name: 'Alice' }]; const posts = [{ id: 'p1', title: 'Hello' }]; findById(users, '1'); findById(posts, 'p1'); function greetById(item) { return ${item.id}: ${item.name}; } console.log(getLength('hello'), findById(users, '1'));
Constraint = minimum
means "T can be any type that has at least length: number." Extra properties are fine.
Unlocks property access
Inside the function, T is guaranteed to have the constraint's properties. x.length compiles because the constraint says so.
Preserves caller's type
The caller still gets back the exact type passed in. findById(users, '1') returns User | undefined, not HasId | undefined.
Multiple via &
— T must satisfy both shapes. requires both properties.
Constraints are promises, not restrictions on you. Beginners think "constraint" means "narrow T down." It doesn't — it means "promise the compiler that T has at least these properties, and I'll use them." The caller can still pass anything that satisfies the promise. findById works for User, Post, Comment, or any future type with an id: string. The constraint is the smallest contract that lets the function do its job.
✅ Solution 2 — keyof constraint (typed property access)
The pattern behind every typed getter.
If you want a function that takes an object and a key, returns the value at that key, and preserves the specific type — keyof is the constraint. K extends keyof T means "K must be one of T's keys," and the return type T[K] is the exact type of the value at that key. This is the most-used generic pattern in library code.
interface User { id: string; name: string; age: number; email: string; } // ─── The typed getter ──────────────────────── function getextends keyof T>(obj: T, key: K): T[K] { return obj[key]; } const user: User = { id: '1', name: 'Alice', age: 28, email: 'a@x.com' }; const n = get(user, 'name'); // n: string — exact type const a = get(user, 'age'); // a: number — exact type const e = get(user, 'email'); // e: string // get(user, 'foo'); // ❌ 'foo' not a key of User // ─── Pluck — pull a field from an array ────── function pluck extends keyof T>(items: T[], key: K): T[K][] { return items.map(item => item[key]); } const users: User[] = [ { id: '1', name: 'Alice', age: 28, email: 'a@x.com' }, { id: '2', name: 'Bob', age: 34, email: 'b@x.com' } ]; const names = pluck(users, 'name'); // string[] const ages = pluck(users, 'age'); // number[] // pluck(users, 'foo'); // ❌ // ─── Set a property by key ─────────────────── function setProp extends keyof T>(obj: T, key: K, value: T[K]): T { return { ...obj, [key]: value }; } const updated = setProp(user, 'name', 'Alicia'); // ✅ value must be string // setProp(user, 'name', 42); // ❌ must be string // setProp(user, 'age', 'x'); // ❌ must be number // ─── Omit by key at runtime ────────────────── function omitKey extends keyof T>(obj: T, key: K): Omit { const { [key]: _, ...rest } = obj; return rest as Omit ; } const noEmail = omitKey(user, 'email'); // noEmail: Omit = { id, name, age } console.log(n, a, names, ages);
function get(obj, key) { return obj[key]; } const user = { id: '1', name: 'Alice', age: 28, email: 'a@x.com' }; const n = get(user, 'name'); const a = get(user, 'age'); const e = get(user, 'email'); function pluck(items, key) { return items.map(item => item[key]); } const users = [ { id: '1', name: 'Alice', age: 28, email: 'a@x.com' }, { id: '2', name: 'Bob', age: 34, email: 'b@x.com' } ]; const names = pluck(users, 'name'); const ages = pluck(users, 'age'); function setProp(obj, key, value) { return { ...obj, [key]: value }; } const updated = setProp(user, 'name', 'Alicia'); function omitKey(obj, key) { const { [key]: _, ...rest } = obj; return rest; } const noEmail = omitKey(user, 'email'); console.log(n, a, names, ages);
K extends keyof T
K must be one of T's keys. get(user, 'name') works; get(user, 'foo') fails. The constraint IS the check.
T[K] — indexed access
The return type is the exact type of the value at key K. get(user, 'age') returns number, not string | number.
Value parameter matches key
setProp — value must match the key's type. 'name' requires a string.
The library pattern
This is how lodash's get, Immer's drafts, and every typed object utility work. Learn it once, recognize it everywhere.
✅ Solution 3 — Constrain to a union
When T must be one of a fixed set of primitive types.
Sometimes you don't want any object — you want one of a specific set of types. restricts T to strings or numbers. This is a constraint too, just a narrower one. It's how you write sorting functions, comparison helpers, and any generic that only works with a subset of types.
// ─── Constrain to primitives ───────────────── type Sortable = string | number; function sortArrayextends Sortable>(arr: T[]): T[] { return [...arr].sort(); } sortArray([3, 1, 2]); // ✅ number[] sortArray(['c', 'a', 'b']); // ✅ string[] // sortArray([{ id: 1 }]); // ❌ object not Sortable // ─── Constrain to a literal union ──────────── type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'; function request extends HttpMethod>(url: string, method: M): void { console.log(method, url); } request('/api/users', 'GET'); // ✅ request('/api/users', 'POST'); // ✅ // request('/api/users', 'PATCH'); // ❌ 'PATCH' not in HttpMethod // ─── Constrain to "has callable method" ────── function callIt extends { (): void }>(fn: T): void { fn(); // ✅ T is guaranteed callable } callIt(() => console.log('hi')); // ✅ // callIt('not a function'); // ❌ // ─── Constraint referencing another param ──── function hasKey extends keyof T>(obj: T, key: K): boolean { return key in obj; } // K's constraint depends on T — that's fine. // TypeScript fills in T first, then constrains K. const user = { id: '1', name: 'Alice' }; hasKey(user, 'name'); // ✅ K='name' // hasKey(user, 'foo'); // ❌ 'foo' not a key // ─── Constraints compose with utility types ── function ensureId extends { id?: string }>(obj: T): T & { id: string } { return { ...obj, id: obj.id ?? crypto.randomUUID() }; } const withId = ensureId({ name: 'x' }); // withId: { name: string; id: string } console.log(sortArray([3, 1, 2]), withId);
function sortArray(arr) { return [...arr].sort(); } sortArray([3, 1, 2]); sortArray(['c', 'a', 'b']); function request(url, method) { console.log(method, url); } request('/api/users', 'GET'); request('/api/users', 'POST'); function callIt(fn) { fn(); } callIt(() => console.log('hi')); function hasKey(obj, key) { return key in obj; } const user = { id: '1', name: 'Alice' }; hasKey(user, 'name'); function ensureId(obj) { return { ...obj, id: obj.id ?? crypto.randomUUID() }; } const withId = ensureId({ name: 'x' }); console.log(sortArray([3, 1, 2]), withId);
Union constraints
— T is one of those. Useful for primitives-only generics (sorting, comparison, hashing).
Literal union constraints
— M is one of the literals. The most precise constraint: catches typos at compile time.
Callable constraint
— T must be callable. Lets you call T() inside the function without knowing its exact signature.
Cross-parameter constraints
— K's constraint depends on T. TypeScript resolves T first, then constrains K accordingly. Very common.
⚠️ Common mistakes
The generic constraint bugs that cost real time.
function f fails — T might be a number. Fix: .
any instead of constrainingfunction f(x: any) { return x.length; } works but loses all type info. Fix: generic + constraint. Same length, full safety.
when you only need id. Fix: — minimal constraint, maximum reuse.
extends is a syntax error. Fix: — combine with intersection.
keyof in cross-parameter constraints — K must be a key of T. Without keyof, K is unconstrained and T[K] fails. Fix: use the pattern exactly.
when you only access id. Fix: constrain only what you use. Smaller constraint = more types allowed = more reuse.
— T is inferred but T[K] doesn't apply. Fix: for primitives, constraining is fine; for objects with keys, use keyof.
function f — T is inferred from both; a string and number conflict. Fix: constrain to the union, or use two parameters .
❓ Frequently asked
How do I constrain a generic type in TypeScript?
Use extends after the type parameter: function getLength
Why do I need to constrain generics?
An unconstrained T could be anything — including types that don't have the property you want to access. Constraining tells the compiler 'T has at least a length property', which unlocks the access. Constraints are how you promise what T can do.
What is a keyof constraint?
keyof T produces a union of T's keys. Using it as a constraint: function get
Can a generic have multiple constraints?
Not with multiple extends clauses. But you can combine constraints with intersection: