Default Generic Type Parameters
Default type parameters let callers omit type arguments when a sensible fallback exists. Learn how to set defaults for generic functions, interfaces, and classes.
TypeScript default generic type parameters give a fallback type that the compiler uses when the caller does not provide one. They work the same way as default function parameters: if the type argument is missing, use the fallback instead.
You set a default with an equals sign after the type parameter name:
function createContainer<T = string>(value: T): { data: T } {
return { data: value };
}
const a = createContainer("hello");
const b = createContainer<number>(42);The first call omits the type argument, so TypeScript uses the default: T is string. The second call explicitly provides number, overriding the default.
Without the default, the first call would still work because TypeScript infers the type from the argument. The default matters most when inference alone would produce an unexpected type, or when the function can be called with no arguments at all.
When Defaults Are Useful
Defaults are most useful in three situations:
1. The function has no required arguments to infer from:
function createList<T = string>(): T[] {
return [];
}
const list = createList();Without the default, TypeScript would infer T as unknown, and the list would be an unknown array. The default makes it a string array when the caller does not specify.
2. One type is far more common than any other:
interface ApiResponse<T = { ok: boolean }> {
data: T;
status: number;
}
const defaultResponse: ApiResponse = {
data: { ok: true },
status: 200,
};
const userResponse: ApiResponse<{ name: string }> = {
data: { name: "Ada" },
status: 200,
};Most responses might carry the same shape, so setting a default lets callers write the interface name without angle brackets every time.
3. The default depends on another type parameter:
function pair<T, U = T>(first: T, second?: U): [T, U] {
return [first, (second ?? first) as U];
}
const p1 = pair(42);
const p2 = pair<string, number>("hello", 42);The second type parameter defaults to the first, so when the caller provides only one argument, both elements have the same type. The as U cast is needed because TypeScript cannot prove that the fallback expression matches U on its own, even though the default relationship guarantees it.
This pattern is common when one type parameter is a natural extension of another.
Defaults on Interfaces and Classes
Defaults work the same way on generic interfaces and classes:
interface Container<T = string> {
value: T;
}
const c1: Container = { value: "hello" };
const c2: Container<number> = { value: 42 };The same default syntax works on classes, and it applies to every instance created without an explicit type argument. For more on how generic classes relate to generic interfaces in TypeScript, see that article:
class Stack<T = string> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
}
const stringStack = new Stack();
const numberStack = new Stack<number>();Calling new Stack() creates a stack of strings because of the default. new Stack<number>() overrides it with a number stack.
Rules for Default Type Parameters
TypeScript enforces a few rules to keep defaults predictable:
Required parameters before optional ones:
function example<T, U = string>(a: T, b?: U): [T, U] {
return [a, b ?? "default"];
}The first parameter has no default and must come first. The second has a default and must come after all required type parameters.
Writing the default first, before a required type parameter, breaks that rule and fails at the declaration:
function example<T = string, U>(a: T, b: U): [T, U] {
return [a, b];
}TypeScript checks this rule at the declaration itself, not when someone calls the function, so the mistake never reaches a caller. The compiler reports it as:
Required type parameters may not follow optional type parameters.A default type also has to satisfy any constraint on the type parameter, the same way an explicit type argument does. This example uses a default that fits the constraint:
interface HasLength {
length: number;
}
function getLength<T extends HasLength = string>(value: T): number {
return value.length;
}The default string satisfies the constraint because strings have a length property. Swapping the default to a type that does not satisfy the constraint fails immediately, before the function is ever called:
function getLength<T extends HasLength = number>(value: T): number {
return value.length;
}Numbers do not have a length property, so the default itself fails the constraint check, and TypeScript reports the mismatch at the declaration:
Type 'number' does not satisfy the constraint 'HasLength'.Defaults are also only a fallback, not a fixed assignment. They apply only when TypeScript has no other way to determine the type, which is a rule worth seeing directly:
function identity<T = string>(value: T): T {
return value;
}
const result = identity(42);Even though the default is string, TypeScript infers T as number from the argument. The default only kicks in when TypeScript cannot infer the type or when the caller omits the argument entirely.
Defaults and Inference Together
When a function has both inference and defaults, TypeScript uses inference first:
function wrap<T = string>(value?: T): T | undefined {
return value;
}
const a = wrap("hello");
const b = wrap();For the first call, TypeScript infers the type from the argument. The default is not used. For the second call, there is no argument to infer from, so TypeScript falls back to the default: the return type is string | undefined.
Common Mistakes
Putting a default before a required parameter:
function fn<T = string, U>(a: T, b: U): void {}This fails at the declaration. Move the required parameter before the default.
Assuming the default always applies even when inference succeeds:
function echo<T = string>(value: T): T {
return value;
}
const result = echo(42);result is number, not string. Inference wins over the default when an argument is provided.
Setting a default that does not match the constraint:
function getLength<T extends { length: number } = number>(value: T): number {
return value.length;
}This is a compile error. Make sure the default type satisfies the constraint.
For more on how constraints and type parameters work together, see generic constraints in TypeScript.
Rune AI
Key Insights
- Set a default with
<T = DefaultType>after the type parameter name. - Callers can omit the type argument and TypeScript uses the default.
- Defaults must appear after required type parameters, just like function parameter defaults.
- A default type parameter can reference earlier type parameters, e.g.
<T, U = T[]>. - Defaults are compile-time only and do not affect the emitted JavaScript.
Frequently Asked Questions
Can a default type parameter reference an earlier type parameter?
Do required type parameters have to come before optional ones?
Can I add a default to an existing type parameter through declaration merging?
Conclusion
Default type parameters make generics easier to use by letting callers skip type arguments when a common fallback exists. They are most useful when one type is used far more often than any other, or when the default can be computed from other type parameters. Follow the same rules as function default parameters: optional parameters go after required ones, and the default must satisfy any constraint on the type parameter.
More in this topic
TypeScript Compiler Explained
The TypeScript compiler (tsc) turns .ts files into .js files while checking types. Learn what it does, how to use it, and how it fits your workflow.
Set Up ESLint for TypeScript
Install and configure ESLint with typescript-eslint to catch bugs and enforce consistent code style in your TypeScript project.
Pick Utility Type in TypeScript
Pick creates a new type by selecting only the properties you name from an existing type. Use it to build focused types for specific views, API responses, or function parameters.