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.

5 min read

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:

typescripttypescript
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:

typescripttypescript
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:

typescripttypescript
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:

typescripttypescript
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:

typescripttypescript
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:

typescripttypescript
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:

typescripttypescript
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:

typescripttypescript
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:

texttext
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:

typescripttypescript
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:

typescripttypescript
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:

texttext
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:

typescripttypescript
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:

typescripttypescript
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:

typescripttypescript
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:

typescripttypescript
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:

typescripttypescript
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

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.
RunePowered by Rune AI

Frequently Asked Questions

Can a default type parameter reference an earlier type parameter?

Yes. You can write `<T, U = T[]>` where the default for U depends on T. This lets the default adapt to whatever the caller provides for T.

Do required type parameters have to come before optional ones?

Yes. Any type parameter without a default must appear before any type parameter with a default. TypeScript enforces this rule when you declare the generic.

Can I add a default to an existing type parameter through declaration merging?

Yes. When an interface or class declaration merges with an earlier declaration, the merged declaration can add a default to an existing type parameter or introduce a new type parameter as long as it provides a default.

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.