Generics Basics

Introduction

Generics let you write functions, classes, and types that work with many types while keeping compile-time safety. Without them, you either duplicate code for each type or fall back to any and lose checking. This chapter introduces generic functions, interfaces, and classes—the core tool for reusable typed APIs.

Prerequisites

The Problem Generics Solve

typescript
function first(arr: any[]): any {
  return arr[0];
}
 
const n = first([1, 2, 3]);
const s = first(["a", "b"]);
// n and s are any—no autocomplete or errors on misuse

With a generic:

typescript
function first<T>(arr: T[]): T | undefined {
  return arr[0];
}
 
const num = first([1, 2, 3]);
const str = first(["a", "b"]);

Code explanation:

  • T is a type parameter filled in when you call first
  • num is number | undefined; str is string | undefined

Generic Functions

Syntax: type parameter in angle brackets before parameters.

typescript
function wrap<T>(value: T): { value: T } {
  return { value };
}
 
const boxed = wrap("hello");

Explicit type argument (optional when inference works):

typescript
const boxedNumber = wrap<number>(42);

Code explanation:

  • Usually TypeScript infers T from arguments
  • Specify <number> when inference fails or you need a specific type

Multiple type parameters

typescript
function pair<A, B>(a: A, b: B): [A, B] {
  return [a, b];
}
 
const p = pair("id", 1);

p is [string, number].

Generic Interfaces

Code explanation:

  • T stands for the payload type
  • One interface definition serves all response data shapes

Generic Classes

Code explanation:

  • Stack<number> only accepts numbers in push
  • pop returns number | undefined

Generic Type Aliases

typescript
type Result<T, E = Error> =
  | { success: true; value: T }
  | { success: false; error: E };
 
function ok<T>(value: T): Result<T> {
  return { success: true, value };
}

Default type parameters appear in the next chapter; here E defaults to Error.

Inference in Generic Calls

typescript
function map<T, U>(items: T[], fn: (item: T) => U): U[] {
  return items.map(fn);
}
 
const lengths = map(["a", "bb"], (s) => s.length);

T is string, U is number, result is number[].

Generics vs any

anyGeneric <T>
Type checkingOff for that valuePreserved per call
Relationships between args/returnsLostMaintained
Refactor safetyLowHigh

Prefer generics for containers, utilities, and API wrappers.

Practical Example: Simple Cache

extends on TKey is a constraint (next chapter).

Rules of Thumb

  1. Name type parameters clearly: T, TItem, TKey, TValue—not single letters only in large APIs.
  2. Start without generics; add them when you see duplicated types or any.
  3. Let inference work; add explicit <T> at call sites when errors are unclear.

FAQ

How many type parameters is too many?

If you have more than two or three, consider an options object type instead.

Can generics be recursive?

Yes, for example type Tree<T> = { value: T; children: Tree<T>[] }—used in advanced structures.

Do generics slow down runtime?

No. They are erased at compile time—no runtime type parameter in standard JS output.

Generic function vs overload?

Generics unify one implementation. Overloads document distinct call shapes—pick based on whether behavior is the same after narrowing.

Why T[] instead of Array<T>?

Same meaning; style preference. Be consistent in your project.

Can React components be generic?

Yes (function List<T>(props: { items: T[] }) { ... }), with some syntax quirks in TSX—framework docs help when you reach UI code.