Skip to content
TypeScript ts generics 3 min read

Multiple Type Parameters

A generic is not limited to a single type parameter. When a function or type relates several independent types — a key and a value, an input and a transformed output, two halves of a pair — you declare multiple type parameters separated by commas. Each is inferred independently from the call site, and they can reference one another through constraints. This page covers declaring several parameters, how inference resolves each, parameters that depend on others, and naming conventions that keep multi-parameter generics readable.

Declaring more than one parameter

List type parameters in angle brackets, separated by commas. A classic example is pair, which combines two values of possibly different types into a tuple, keeping each type distinct.

function pair<A, B>(first: A, second: B): [A, B] {
  return [first, second];
}

const p = pair("id", 42); // p: [string, number]
const q = pair(true, [1]); // q: [boolean, number[]]

A and B are resolved separately — A becomes string and B becomes number for the first call. The tuple return type preserves both, so destructuring const [k, v] = p gives k: string and v: number.

Independent inference

Each parameter is inferred from wherever it appears in the signature. A map function over arrays is the textbook case: T comes from the input array, and U is inferred from the callback’s return type.

function mapArray<T, U>(items: T[], fn: (item: T) => U): U[] {
  return items.map(fn);
}

const lengths = mapArray(["a", "bb", "ccc"], (s) => s.length);
// T = string (from the array), U = number (from fn's return) → number[]

You did not annotate either parameter; the compiler derived T from the array elements and U from what the callback returns. This independence is what lets one generic describe an entire transformation.

Parameters that depend on each other

Type parameters can constrain one another. The keyof pattern from the previous page is the most common: K is constrained to the keys of T, so the two parameters are linked rather than independent.

function merge<T extends object, U extends object>(a: T, b: U): T & U {
  return { ...a, ...b };
}

const merged = merge({ id: 1 }, { name: "Ada" });
// merged: { id: number } & { name: string }

function entries<T, K extends keyof T>(obj: T, keys: K[]): [K, T[K]][] {
  return keys.map((k) => [k, obj[k]]);
}

In merge, both parameters are constrained to object but stay distinct, and the intersection T & U describes the combined result. In entries, K depends on T, demonstrating how later parameters can reference earlier ones in their constraints.

Mixing inference and explicit arguments

When you supply type arguments explicitly, you must provide them left to right; you cannot skip one. If only a later parameter needs pinning, consider reordering parameters or using a default (covered next) so inference can handle the rest.

function convert<TIn, TOut>(value: TIn, fn: (v: TIn) => TOut): TOut {
  return fn(value);
}

const n = convert<string, number>("42", Number); // both explicit
const m = convert("42", Number);                 // both inferred → number
// const x = convert<string>("42", Number);       // ❌ Error: expected 2 type arguments

Because explicit type arguments are positional and all-or-nothing (short of defaults), prefer inference whenever the compiler can resolve every parameter from the values.

ScenarioParametersRelationship
Combine two values<A, B>independent
Transform a collection<T, U>U derived from a callback
Key-based access<T, K extends keyof T>K depends on T
Merge objects<T, U>both constrained, result is T & U

If a generic grows past three or four type parameters, it is often a sign the function is doing too much. Consider splitting it, grouping inputs into an object type, or applying defaults to the rarely-overridden parameters.

Best Practices

  • Add a type parameter for each independent type the signature must relate.
  • Rely on independent inference; annotate only when the compiler cannot resolve a parameter.
  • Link parameters with constraints (K extends keyof T) when one depends on another.
  • Remember explicit type arguments are positional and all-or-nothing without defaults.
  • Use descriptive names (TIn/TOut, TKey/TValue) once single letters get ambiguous.
  • Keep the count low; many parameters usually signal a design that should be split.
Last updated June 29, 2026
Was this helpful?