Skip to content

Generic Constraints

An unconstrained type parameter can be any type at all. That generality is useful, but it means TypeScript cannot let you use any property or method on a value of that type — it could be string, number, an object, or anything else. Constraints let you say “T can be any type, as long as it has at least these properties.”

The problem an unconstrained parameter creates

Section titled “The problem an unconstrained parameter creates”

Suppose you want a function that reads a length property from its argument:

function logLength<T>(value: T): void {
console.log(value.length); // Error: Property 'length' does not exist on type 'T'
}

TypeScript rejects this. T could be number, which has no length. The constraint is missing — nothing tells TypeScript that T must have a length property.

Use extends after the type parameter to require a minimum shape:

function logLength<T extends { length: number }>(value: T): void {
console.log(value.length); // ✓ — T is guaranteed to have length
}
logLength('AceIt'); // ✓ — string has length
logLength([1, 2, 3]); // ✓ — arrays have length
logLength({ length: 5 }); // ✓ — object with length satisfies the constraint
logLength(42); // Error: number does not have 'length'

T extends { length: number } says: T can be any type, as long as it has at least a length: number property. TypeScript narrows what T can be at call sites and allows access to length inside the body.

Any type can serve as a constraint — including your own interfaces:

interface HasQuestion {
question: string;
}
function logQuestion<T extends HasQuestion>(item: T): void {
console.log(item.question); // ✓ — T is guaranteed to have 'question'
}
logQuestion({ question: 'What planet?', correctAnswer: 'Mercury' }); // ✓
logQuestion({ score: 7 }); // Error: missing 'question'

T can be any object that has at least question: string. Extra properties are fine — structural typing still applies.

keyof T produces a union of the property names of T. Combined with a type parameter, it describes a function that safely retrieves any property of an object:

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const q: TriviaQuestion = { /* ... */ };
const text = getProperty(q, 'question'); // string — inferred from TriviaQuestion['question']
const answers = getProperty(q, 'allAnswers'); // string[] — inferred from TriviaQuestion['allAnswers']
getProperty(q, 'score'); // Error: 'score' is not a key of TriviaQuestion

K extends keyof T constrains K to only the keys that exist on T. The return type T[K] is the type of that specific property — TypeScript infers it precisely at every call site. This is called an indexed access type.

The object type accepts any non-primitive value — objects, arrays, functions, but not string, number, or boolean:

function deepClone<T extends object>(value: T): T {
return JSON.parse(JSON.stringify(value));
}
deepClone({ score: 7, total: 10 }); // ✓
deepClone([1, 2, 3]); // ✓
deepClone('hello'); // Error: string is not assignable to 'object'

The shuffle<T> function from Lesson 02 has no constraint because T can genuinely be any type — it only needs to exist in an array. No properties of T are accessed inside the function body:

function shuffle<T>(arr: T[]): T[] {
return [...arr].sort(() => Math.random() - 0.5);
}

This is correct — do not add constraints you do not need. Constraints reduce the types a caller can pass. Constrain only when the function body requires access to specific properties.

Add a constraint when:

  • The function body accesses a property or method on the type parameter — constrain to a shape that has it.
  • The function should only accept objects, not primitives — constrain with object.
  • The function reads a specific key from an object — use K extends keyof T.

Do not constrain when:

  • The function treats T as an opaque value — stores, returns, or puts it in an array without accessing its shape.

In a scratch file constraints.ts:

  1. Write function first<T>(arr: T[]): T | undefined — no constraint needed, just returns the first element.
  2. Write function getField<T extends object, K extends keyof T>(obj: T, key: K): T[K]. Call it with a TriviaQuestion and 'category'. Verify the return type is string.
  3. Write function printAll<T extends { toString(): string }>(items: T[]): void — calls item.toString() on each. Call it with an array of numbers and an array of strings.
  4. Try removing the constraint from (3) and observe the error.
  • T extends SomeType constrains what types a caller can supply for T.
  • Constraints allow the function body to access properties or methods guaranteed by the constraint.
  • K extends keyof T restricts K to the known keys of T — used with indexed access types (T[K]).
  • object is a constraint that rejects primitives while accepting any non-primitive.
  • Only add constraints the function body requires — unnecessary constraints reduce caller flexibility.