Generic Constraints & Defaults
A generic constraint, written <T extends Constraint> , restricts a type parameter to types that have a required shape so the function body can use those members safely, while a default type parameter, <T = SomeType> , supplies a fallback type when the caller doesn't provide or imply one.
Learn Generic Constraints & Defaults in our free TypeScript course — an interactive lesson with runnable examples, a practice exercise and a quick reference.
Part of the free TypeScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn
💡 Real-World Analogy
An unconstrained generic is a job posting that says "anyone may apply". You can't ask applicants to do anything specific because you don't know what they can do. A constraint is a job requirement — "must have a driving licence" ( extends { length: number } ). Now you can confidently ask every applicant to drive (read .length ), and anyone without the licence is rejected at the door (a compile error). A default type is the "if unspecified, we'll assume X" line on the form — a sensible fallback so the common case needs no extra paperwork.
1. <T extends Constraint>
A bare <T> could be anything, so you can't touch any of its properties. Adding extends a shape promises those members exist. The function stays generic — it accepts strings, arrays, or any object with a length — yet the body can safely read .length :
2. Default Parameters & Multiple Constraints
A default type parameter <T = string> is used when the caller neither specifies T explicitly nor gives an argument TypeScript can infer it from. You can also declare several type parameters, each with its own constraint, and combine constraints with defaults:
3. Constraining to keyof — a Safe Getter
The most useful constraint pairs two parameters: <T, K extends keyof T> . The second parameter is forced to be a real key of the first, so a typo becomes a compile error and the return type T[K] is exactly the type of that property:
🎯 Your Turn
Implement the body of a function whose generic is constrained to anything with a length . Fill in the blanks and match the expected output.
Common Errors (and the fix)
- ❌ "Property 'x' does not exist on type 'T'": you accessed a member on an unconstrained generic. ✅ Add a constraint that promises it: <T extends { x: ... }> .
- ❌ Passing a bad key: getProp(user, "naem") → "Argument of type '"naem"' is not assignable to parameter of type 'keyof ...'". ✅ Fix the typo; the keyof constraint caught it for you.
- ❌ Constraint too loose: <T extends object> then doing arithmetic — object doesn't have + . ✅ Constrain to exactly what you use ( { value: number } ).
- ❌ Default never applies: <T = string> seems ignored because an argument let TypeScript infer a different T . ✅ Defaults only kick in when T can't be inferred or specified.
- ❌ Out-of-order parameters: <K extends keyof T, T> errors because K references T before it's declared. ✅ Declare T first: <T, K extends keyof T> .
Pro Tips
- 💡 Constrain to exactly the members you use — no looser, no tighter. It keeps the function maximally reusable yet still safe.
- 💡 Return T , not the constraint , so callers keep their precise types.
- 💡 <T, K extends keyof T> is the workhorse pattern behind type-safe getters, setters, and column selectors — memorise it.
- 💡 Combine constraint + default ( <T extends object = {}> ) when a type must be restricted but also has a sensible no-argument fallback.
Frequently Asked Questions
Mini-Challenge: pluck
Build a type-safe pluck that extracts one property from every object in an array — the runtime form of <T, K extends keyof T> . Follow the outline, run it, and match the example output.
🎉 Lesson Complete
- ✅ <T extends Constraint> limits a type parameter so its members are safe to use
- ✅ Return T to preserve the caller's exact type
- ✅ <T = SomeType> supplies a fallback type when T can't be inferred
- ✅ Declare multiple constrained parameters, e.g. <A extends object, B extends object>
- ✅ <T, K extends keyof T> + return T[K] builds a type-safe getter
- ✅ A parameter can be constrained by another parameter declared before it
- ✅ Next lesson: keyof, typeof & Lookup Types
Practice quiz
What does '<T extends { length: number }>' promise inside the function body?
- T is always a string
- T is an array
- T has a numeric 'length' property you can safely use
- T is unknown
Answer: T has a numeric 'length' property you can safely use. The constraint guarantees the member exists, so reading a.length is provably safe.
Why can't you read a property off an unconstrained '<T>'?
- T could be anything (e.g. a number), so no property is provably present
- Generics are slow
- TypeScript forbids all generics
- It needs a default
Answer: T could be anything (e.g. a number), so no property is provably present. Without a constraint, the compiler knows nothing about T, so member access is rejected.
What is the difference between a constraint and a default type parameter?
- They are the same
- A default limits T; a constraint sets a fallback
- Both supply fallbacks
- A constraint LIMITS what T can be; a default supplies a FALLBACK when T is not given/inferred
Answer: A constraint LIMITS what T can be; a default supplies a FALLBACK when T is not given/inferred. <T extends U> restricts T; <T = U> provides a fallback only when T is not specified or inferred.
What does 'keyof T' produce?
- The values of T
- The union of all of T's property names
- A new object
Answer: The union of all of T's property names. keyof T is the union of T's property name literals.
In 'function getProp<T, K extends keyof T>(obj: T, key: K)', what is the return type?
The lookup type T[K] is exactly the type of the selected property.
What does the K extends keyof T constraint catch at compile time?
- Slow code
- Missing return statements
- A bad/typo key like getProp(user, "naem")
- Circular types
Answer: A bad/typo key like getProp(user, "naem"). Passing a key that is not a real property of T becomes a compile error, not a runtime undefined.
When does a default type parameter '<T = string>' actually apply?
- Always
- Only when T is neither specified explicitly nor inferable from an argument
- Only with keyof
- Never
Answer: Only when T is neither specified explicitly nor inferable from an argument. If TypeScript can infer T from an argument, the inferred type wins over the default.
What is the return type of 'merge<A extends object, B extends object>(a: A, b: B)'?
- object
- A | B
- unknown
- A & B
Answer: A & B. The intersection A & B has all properties of both inputs, keeping the merged object fully typed.
Why declare 'T' before 'K' in '<T, K extends keyof T>'?
- Alphabetical order
- A parameter can only be constrained by ones declared before it
- K is faster
- It is optional
Answer: A parameter can only be constrained by ones declared before it. <K extends keyof T, T> errors because K references T before it is declared.
How do you both restrict a type parameter and give it a fallback?
- <T = object extends {}>
- <T: object | {}>
- <T extends object = {}>
- You cannot combine them
Answer: <T extends object = {}>. <T extends object = {}> means T must be an object and defaults to {} when not provided.
Continue this course
- Previous: Conditional Types & infer
- Next: keyof, typeof & Lookup Types