Skip to content
TypeScript ts generics 4 min read

Generic Functions

Generics let a function work over many types while preserving the relationship between its inputs and outputs. Instead of falling back to any and throwing away type information, you introduce a type parameter — a placeholder that TypeScript fills in from the call site. The result is code that is reusable like an untyped helper but as safe as a hand-written, fully annotated one. This page covers declaring type parameters, how inference resolves them, explicit type arguments, and the modern const type parameter from TypeScript 5.0.

Declaring a type parameter

A generic function lists its type parameters in angle brackets after the function name. The classic example is identity, which returns its argument unchanged. The parameter T ties the input type to the output type, so the caller gets back exactly what they put in.

function identity<T>(value: T): T {
  return value;
}

const a = identity("hello"); // a: string
const b = identity(42);      // b: number

Compare this to function identity(value: any): any, which compiles but erases all type information — a and b would both be any, and you would lose autocomplete and error checking on the result. With T, the return type tracks the argument precisely.

Type argument inference

In most cases you never write the type argument explicitly. TypeScript infers it from the values you pass, which keeps generic code feeling lightweight. Inference works even for complex shapes like arrays and tuples.

function firstElement<T>(arr: T[]): T | undefined {
  return arr[0];
}

const n = firstElement([1, 2, 3]);        // n: number | undefined
const s = firstElement(["a", "b"]);       // s: string | undefined
const u = firstElement([1, "two", true]); // u: string | number | boolean | undefined

The compiler scans the argument, decides T is number for [1, 2, 3], and substitutes it throughout the signature. Inference is the reason generics rarely add visual noise at the call site.

Supplying type arguments explicitly

Sometimes inference cannot determine T, or you want to widen or pin it deliberately. You can pass the type argument inside angle brackets at the call site. This is common when the type appears only in the return position.

function createArray<T>(length: number, fill: T): T[] {
  return Array.from({ length }, () => fill);
}

const nums = createArray<number>(3, 0); // number[]
const strs = createArray(3, "x");       // string[] — inferred, no need for <string>

// Useful when nothing to infer from:
const empty = identity<string[]>([]);   // string[], not never[]

Reach for explicit type arguments only when inference is wrong or impossible. Over-annotating generic calls makes code harder to read and defeats one of the main benefits of generics.

Generic arrow functions and type aliases

Arrow functions take type parameters too, and you can capture a generic function’s signature in a type alias for reuse. In a .tsx file, write the parameter as <T,> (with a trailing comma) so the compiler does not mistake it for a JSX tag.

const wrap = <T>(value: T): T[] => [value];

type Mapper = <T, U>(items: T[], fn: (item: T) => U) => U[];

const map: Mapper = (items, fn) => items.map(fn);
const lengths = map(["a", "bb"], (s) => s.length); // number[]

Note that the type parameters belong to the function, not the alias: Mapper itself is not generic, but every value of type Mapper is a generic function resolved per call.

const type parameters (TS 5.0)

By default TypeScript widens inferred literals — "primary" becomes string, and arrays become mutable arrays. A const type parameter, added in TypeScript 5.0, tells the compiler to infer the narrowest type, as if the caller had written as const, without requiring them to do so.

function asTuple<const T extends readonly unknown[]>(...items: T): T {
  return items;
}

const t = asTuple("a", "b", "c"); // readonly ["a", "b", "c"]

function pick<const T>(value: T): T {
  return value;
}

const config = pick({ mode: "dark" }); // { readonly mode: "dark" }

Without const, t would be string[] and config.mode would be string. The const modifier is ideal for builder-style APIs where preserving exact literal types matters.

When to reach for generics

SituationUse a generic?Why
Input type equals output typeYesPreserve the relationship with T
Function works on any element typeYesOne implementation, many types
Type appears in only one position, unrelatedNoA union or unknown is clearer
You would otherwise reach for anyYesKeep type safety
The concrete type is always the sameNoJust annotate it directly

Best Practices

  • Use a type parameter when two or more positions in a signature must share the same type.
  • Prefer inference; add explicit type arguments only when the compiler cannot resolve them.
  • Name parameters meaningfully (TItem, TKey) once a single T becomes ambiguous.
  • Never substitute any for a generic — you lose every safety guarantee generics provide.
  • Use const type parameters to preserve literal and tuple types in builder-style APIs.
  • In .tsx files write <T,> to disambiguate generic arrows from JSX.
  • Keep generic signatures readable; if a function needs many parameters, reconsider the design.
Last updated June 29, 2026
Was this helpful?