TypeScript Generics

TypeScript generics let you write functions and types that work with many types while keeping full type safety. A generic uses a type parameter, usually called T, that is filled in when the code is used.

Why Generics?

Without generics you must choose between a specific type (not reusable) and any (no safety). A generic function remembers the type you pass in.

function first<T>(items: T[]): T | undefined {
  return items[0];
}
const n = first([10, 20, 30]);      // n: number | undefined
const s = first(["red", "blue"]);   // s: string | undefined
console.log(n, s?.toUpperCase());
10 RED

Multiple Type Parameters

TypeScript infers the type arguments from the values you pass, so you rarely write them explicitly.

function pair<K, V>(key: K, value: V): [K, V] {
  return [key, value];
}
function mapValues<T, U>(arr: T[], fn: (item: T) => U): U[] {
  return arr.map(fn);
}
console.log(pair("age", 30));
console.log(mapValues(["a", "bb", "ccc"], (s) => s.length));
[ 'age', 30 ]
[ 1, 2, 3 ]

Generic Constraints

Use extends to require that T has certain properties. keyof T restricts a parameter to the keys of T.

function longest<T extends { length: number }>(a: T, b: T): T {
  return a.length >= b.length ? a : b;
}
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}
console.log(longest("apple", "kiwi"));
console.log(longest([1, 2], [1, 2, 3]).length);

const product = { name: "Tea", price: 6 };
console.log(getProp(product, "price"));
// getProp(product, "color"); // Error: '"color"' is not assignable to '"name" | "price"'.
apple
3
6

Generic Interfaces and Types

Generic types are perfect for wrappers like API responses, where the envelope is the same but the data changes. Type parameters can have defaults.

interface ApiResponse<T, E = string> {
  ok: boolean;
  data?: T;
  error?: E;
}
type User = { id: number; name: string };

const res: ApiResponse<User[]> = { ok: true, data: [{ id: 1, name: "Ali" }] };
console.log(res.data?.[0].name);
Ali

Generic Classes

class Stack<T> {
  #items: T[] = [];
  push(item: T) { this.#items.push(item); }
  pop(): T | undefined { return this.#items.pop(); }
  get size() { return this.#items.length; }
}
const history = new Stack<string>();
history.push("/home");
history.push("/tutorials");
// history.push(42); // Error: 'number' is not assignable to 'string'.
console.log(history.pop(), history.size);
/tutorials 1

Frequently asked questions

What does <T> mean in TypeScript?

It declares a type parameter named T, a placeholder for a type that is supplied (or inferred) when the function or type is used.

What is a generic constraint?

A rule written with extends, like T extends { id: number }, that limits which types can be used for T.

When should I use generics instead of any?

Whenever the output type depends on the input type. Generics preserve the type information; any throws it away.