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.
Adding a constraint with extends
Section titled “Adding a constraint with extends”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 lengthlogLength([1, 2, 3]); // ✓ — arrays have lengthlogLength({ length: 5 }); // ✓ — object with length satisfies the constraintlogLength(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.
Constraining to an interface
Section titled “Constraining to an interface”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.
The keyof constraint
Section titled “The keyof constraint”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 TriviaQuestionK 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.
Constraining to object
Section titled “Constraining to object”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'shuffle with a constraint
Section titled “shuffle with a constraint”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.
When to use constraints
Section titled “When to use constraints”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
Tas an opaque value — stores, returns, or puts it in an array without accessing its shape.
Exercise
Section titled “Exercise”In a scratch file constraints.ts:
- Write
function first<T>(arr: T[]): T | undefined— no constraint needed, just returns the first element. - Write
function getField<T extends object, K extends keyof T>(obj: T, key: K): T[K]. Call it with aTriviaQuestionand'category'. Verify the return type isstring. - Write
function printAll<T extends { toString(): string }>(items: T[]): void— callsitem.toString()on each. Call it with an array of numbers and an array of strings. - Try removing the constraint from (3) and observe the error.
T extends SomeTypeconstrains what types a caller can supply forT.- Constraints allow the function body to access properties or methods guaranteed by the constraint.
K extends keyof TrestrictsKto the known keys ofT— used with indexed access types (T[K]).objectis a constraint that rejects primitives while accepting any non-primitive.- Only add constraints the function body requires — unnecessary constraints reduce caller flexibility.