Generics cheatsheet.

Generic functions

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

identity(42);             // T inferred as number
identity<string>("hi");   // explicit

Multiple type params

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

pair("hi", 42);           // [string, number]

Constraints (extends)

function len<T extends { length: number }>(x: T): number {
  return x.length;
}

len("hi");                // ok
len([1, 2, 3]);           // ok
len(42);                  // ERROR: no length

keyof

function pluck<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const u = { id: 1, name: "A" };
pluck(u, "id");           // number
pluck(u, "missing");      // ERROR

Defaults

function box<T = string>(x: T): { value: T } {
  return { value: x };
}

box("hi");                // T = string
box<number>(42);

Generic interfaces / type aliases

interface Result<T, E = Error> {
  ok: boolean;
  value?: T;
  error?: E;
}

type Pair<A, B = A> = [A, B];

Generic classes

class Stack<T> {
  private items: T[] = [];
  push(x: T) { this.items.push(x); }
  pop(): T | undefined { return this.items.pop(); }
}

const s = new Stack<number>();

Conditional types

type IsArray<T> = T extends any[] ? true : false;
type A = IsArray<number[]>;     // true
type B = IsArray<string>;       // false

infer

type ReturnOf<T> = T extends (...args: any[]) => infer R ? R : never;

type R = ReturnOf<() => string>;   // string

// Unwrap promise
type Awaited2<T> = T extends Promise<infer U> ? U : T;

Distribution over unions

type ToArray<T> = T extends any ? T[] : never;
type X = ToArray<string | number>;   // string[] | number[]

Wrap to prevent distribution:

type ToArray<T> = [T] extends [any] ? T[] : never;
type X = ToArray<string | number>;   // (string | number)[]

Mapped types

type Optional<T> = { [K in keyof T]?: T[K] };
type Readonly2<T> = { readonly [K in keyof T]: T[K] };
type Stringify<T> = { [K in keyof T]: string };

Mapped with as (remap keys)

type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

type G = Getters<{ id: number; name: string }>;
// { getId: () => number; getName: () => string }

Generic constraint with default

function getById<T extends { id: number } = { id: number }>(
  items: T[],
  id: number
): T | undefined {
  return items.find(x => x.id === id);
}

Generic factories

function create<T>(Ctor: new () => T): T {
  return new Ctor();
}

class Widget {}
const w = create(Widget);     // Widget

Generic discriminated unions

type Result<T> =
  | { ok: true; value: T }
  | { ok: false; error: string };

function unwrap<T>(r: Result<T>): T {
  if (r.ok) return r.value;
  throw new Error(r.error);
}

NoInfer (5.4+)

function call<T>(value: T, transformer: (x: NoInfer<T>) => T): T {
  return transformer(value);
}

call("hi", (x: string) => x.toUpperCase());
// T inferred from first arg, NOT the second

Without NoInfer, both args contribute to inference — sometimes you want only one.

Higher-kinded patterns (workaround)

TS lacks true HKT, but you can simulate with interfaces:

interface Functor<F> {
  map<A, B>(fa: F & A, f: (a: A) => B): F & B;
}

Use sparingly — usually a sign you’re overcomplicating.

Generic React components

function List<T>({ items, render }: {
  items: T[];
  render: (item: T) => React.ReactNode;
}) {
  return <ul>{items.map((it, i) => <li key={i}>{render(it)}</li>)}</ul>;
}

<List items={[1, 2, 3]} render={(n) => n * 2} />

In .tsx the <T> is ambiguous with JSX. Workaround: <T,> or <T extends unknown>.

Variance (4.7+)

type Animal = { name: string };
type Dog = Animal & { bark(): void };

type Producer<out T> = () => T;       // covariant
type Consumer<in T> = (x: T) => void; // contravariant
type Bivariant<in out T> = ...;

Common mistakes

  • <T> without using T — just remove it.
  • Constraints too loose — T extends object accepts arrays, functions, etc.
  • Distribution surprise — T extends X ? ... : ... over a union.
  • Returning T from T extends Y body — TS doesn’t narrow T inside the conditional.
  • Generic on every function — sometimes a union or overload is clearer.

Read this next

If you want my generic utility library, it’s at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .