Generic Defaults & Inference
Generic defaults give a type parameter a fallback ( <T = string> ) when none is provided, while type inference lets TypeScript figure out a generic's type from the arguments you pass — so you rarely write the angle brackets yourself.
Learn Generic Defaults & Inference 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
Think of a coffee order. Inference is the barista guessing your milk from what you usually get — you don't say it, they just know. A default is the house standard when you don't specify ("regular oat milk unless you say otherwise"). And a const type parameter is the barista writing down your exact order word-for-word ("extra-hot, half-shot") instead of rounding it to "a coffee" — the precise details are kept.
1. Default Type Parameters
You can give a generic a default with <T = SomeType> . If the caller neither supplies T nor gives the compiler something to infer it from, T falls back to that default. Defaults shine on generic interfaces and classes — like ApiResponse<TData = unknown> — so simple callers don't have to spell out a type argument.
2. How Inference Works (and When to Annotate)
Most of the time you never write <...> — TypeScript infers the type argument from the value you pass. identity("hi") infers T = string ; first([true, false]) infers the element type. You must annotate only when there's nothing to infer from — for example a function that returns T[] but takes no T argument.
3. const Type Parameters & Widening
By default, inference widens literals: pass ["red", "green"] and you get string[] , losing the exact values. A const type parameter ( <const T> , TypeScript 5.0) tells the compiler to keep the narrowest literal type — the precise tuple — without scattering as const across every call.
🎯 Your Turn
Mirror a generic default using a default parameter : greet() with no argument should fall back to "guest" . Fill in the two blanks marked ___ , then run it.
Common Errors (and the fix)
- ❌ "Type 'unknown' is not assignable..." — a generic had no argument to infer from. ✅ Annotate the call: make<number>() , or restructure so T can be inferred.
- ❌ Inferred type is too wide ( string instead of "red" ) — inference widened literals. ✅ Use a <const T> type parameter, or as const at the call site.
- ❌ "Type parameter 'T' has a circular default" — a default referenced itself. ✅ Make the default a concrete type, not one that depends on T in a loop.
- ❌ Default placed before a non-default parameter — <T = string, U> is invalid. ✅ Defaulted type parameters must come after required ones, just like function args.
Pro Tips
- 💡 Let inference do the work. Writing explicit type arguments everywhere is usually a sign the API could be designed to infer them.
- 💡 Add a sensible default (often unknown ) to generic interfaces so simple consumers can skip the type argument.
- 💡 Reach for <const T> in builder/config helpers where you want literal keys and values preserved.
- 💡 Constrain and default together : <T extends object = {}> gives both a bound and a fallback.
📚 Keep Learning
- 📖 TypeScript Handbook — Generics
- 📖 TS 5.0 Release Notes — const Type Parameters
- 📖 Handbook — Type Inference
Frequently Asked Questions
Mini-Challenge: A wrap() Helper
No blanks this time — just a brief and a starting outline. Build the helper yourself, run it, and check your output against the example in the comments.
🎉 Lesson Complete
- ✅ <T = string> gives a type parameter a default fallback
- ✅ TypeScript infers type arguments from the values you pass
- ✅ You only annotate when there's nothing to infer from
- ✅ Inference widens literals to general types by default
- ✅ <const T> (TS 5.0) keeps the narrow literal types without as const
- ✅ Next lesson: Function Types, this , Parameters & ReturnType
Practice quiz
What is a default type parameter?
- A runtime default argument
- An interface
- A fallback type for a generic, written like <T = string>
- A const assertion
Answer: A fallback type for a generic, written like <T = string>. <T = string> is the type-level version of a default function parameter.
When does the default in '<T = string>' get used?
- When the caller does not supply T and TypeScript cannot infer it from arguments
- Always
- Only with classes
- Only in strict mode
Answer: When the caller does not supply T and TypeScript cannot infer it from arguments. If T can be inferred from an argument, the inferred type wins; the default applies only otherwise.
How does TypeScript usually figure out a generic's type argument?
- You must always write <...>
- From the tsconfig
- Randomly
- It infers it from the values you pass at the call site
Answer: It infers it from the values you pass at the call site. identity("hi") infers T = string and identity(7) infers T = number, so you rarely write <...>.
When MUST you annotate a type argument yourself?
- Always
With no argument of type T, the compiler has nothing to read T from, so you write make<number>().
If you call 'function make<T>(): T[]' with no annotation, what is T?
- unknown
- string
- never
- any
Answer: unknown. With nothing to infer from and no default, T defaults to unknown.
By default, inference of '["red", "green"]' produces which type?
Inference widens literals, so you get string[] and lose the exact values.
What does a const type parameter '<const T>' (TS 5.0) do?
- Freezes the array at runtime
- Tells the compiler to infer the narrowest literal type instead of widening
- Makes T a constant value
- Requires 'as const' everywhere
Answer: Tells the compiler to infer the narrowest literal type instead of widening. <const T> keeps literal/tuple types, a cleaner alternative to scattering 'as const'.
Does '<const T>' change the runtime value of the array?
- Yes, it freezes it
- Yes, it copies it
- Only in development
- No — it only affects the inferred type
Answer: No — it only affects the inferred type. It changes only the type; the runtime array is exactly the same array.
Where must a defaulted type parameter appear relative to non-defaulted ones?
- Before them
- After them
- Anywhere
- Only alone
Answer: After them. <T = string, U> is invalid; defaulted type parameters must come after required ones.
On a generic interface, why add a default like 'ApiResponse<TData = unknown>'?
- For speed
- To enable inference of TData
- So simple callers can omit the type argument
- It is required syntax
Answer: So simple callers can omit the type argument. A default lets consumers write ApiResponse without spelling out the type argument.
Continue this course
- Previous: DOM & Event Types
- Next: Function Types & this