LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 917 min read

Generics

Learn TypeScript generics: generic functions, interfaces, and classes, constraints with extends, default type parameters, and utility types.

Introduction

Generics let you write a function, interface, or class once, and have it work correctly and safely across many different types — without giving up type checking by falling back to `any`.

What You Will Learn
  • Why generics exist and the problem they solve compared to using any.
  • How to write and call a generic function.
  • How to define generic interfaces and classes.
  • How to constrain a generic type parameter with extends.
  • The most common built-in utility types (Partial, Pick, Omit, Record).

Why Generics?

Imagine a function that returns whatever value you pass into it. Typed as `any`, it would compile, but you'd lose all type information — calling it with a string and getting back `any` means the compiler can no longer help you. A generic type parameter, conventionally named `T`, lets the function's input type flow through to its output type, fully preserved.

Generic Functions

function identity<T>(value: T): T {
return value;
}
const num = identity(42); // inferred as number
const str = identity("hello"); // inferred as string

Generic Interfaces and Classes

Interfaces, type aliases, and classes can all take type parameters too, letting a single reusable shape or implementation adapt to whatever type it's used with.

interface Box<T> {
value: T;
}
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
}
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.pop());
Output

Click Run to see what this code prints.

Constraints with extends

Sometimes a generic function needs to guarantee its type parameter has certain properties — for example, that it has a `.length`. Adding `extends` to the type parameter constrains which types are allowed, while still letting the specific type vary.

function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
console.log(longest("hi", "hello")); // "hello"
console.log(longest([1, 2], [1, 2, 3])); // [1, 2, 3]

Default Type Parameters

A generic type parameter can have a default, used whenever the caller doesn't explicitly supply one — similar in spirit to default function parameters.

interface ApiResponse<T = unknown> {
data: T;
success: boolean;
}
const response: ApiResponse<{ id: number }> = { data: { id: 1 }, success: true };

Common Utility Types

TypeScript ships several built-in generic utility types that transform an existing type instead of making you write it by hand.

Utility TypeWhat It Does
Partial<T>Makes every property of T optional.
Required<T>Makes every property of T required, even if originally optional.
Readonly<T>Makes every property of T readonly.
Pick<T, K>Builds a new type using only the listed keys K from T.
Omit<T, K>Builds a new type with the listed keys K removed from T.
Record<K, V>Builds an object type with keys K, each mapped to value type V.

Common Mistakes

Avoid These Mistakes
  • Reaching for `any` in a reusable function instead of a generic, silently throwing away type information the caller actually needs.
  • Accessing properties on a generic `T` without constraining it with `extends`, then being confused why the compiler rejects it.
  • Adding a generic type parameter to a function or class that never actually varies — a sign the generic wasn't needed at all.
  • Confusing a generic type parameter (a compile-time placeholder like `T`) with a regular runtime function parameter.

Best Practices

  • Reach for a generic whenever a function or class's logic is identical regardless of the specific type involved — a stack, a cache, a fetch wrapper.
  • Constrain generics with `extends` as tightly as the function genuinely needs, and no tighter.
  • Use plain names like `T`, `K`, `V` for simple, well-understood generics; use descriptive names like `TItem` for complex, multi-parameter generics.
  • Learn the built-in utility types (Partial, Pick, Omit, Record) before writing custom equivalents from scratch.

Frequently Asked Questions

No — any throws away all type information, while generics preserve and propagate it through the function or class so the compiler can still fully check your code.

Yes — for example, function pair<A, B>(a: A, b: B): [A, B] takes two independent type parameters.

No — like all TypeScript types, generic type parameters are fully erased. The emitted JavaScript is identical to what you'd write without them.

T is a single value of the generic type; T[] is an array of values of that type — both appear constantly in generic function signatures.

Key Takeaways

  • Generics let a function, interface, or class stay reusable across many types without losing type safety.
  • A generic type parameter (commonly T) is a placeholder filled in at each call site.
  • extends constrains a generic to types with certain properties, without pinning it to one exact type.
  • Built-in utility types like Partial, Pick, Omit, and Record cover most everyday type transformations.
  • Generics are fully erased at compile time and add zero runtime overhead.

Summary

You can now write reusable, fully type-safe functions, interfaces, and classes with generics. Next, you'll learn enums — TypeScript's way of naming a fixed, related set of constant values.

Next Lesson →

Enums