TypeScript

TypeScript Generics👨‍💻

Generics let you write functions, interfaces, and classes that work with any type while keeping full type safety. Every time you write Array<string>, Promise<Response>, or useState<number>(), you're using generics. They're the mechanism that makes code both reusable and type-safe — without forcing you to choose between any (flexible, unsafe) and hardcoded types (safe, rigid).

The core idea is simple: instead of specifying a concrete type, you declare a type parameter — a placeholder that gets filled in when the generic is used. TypeScript then flows that type through the entire function or interface, catching mismatches at compile time.

Key Takeaways

  • 1A type parameter (typically `T`) acts as a variable for types — it gets resolved when the generic is called or instantiated
  • 2TypeScript infers type arguments from the values you pass in most cases, so you rarely need to specify them explicitly
  • 3Generic constraints (`extends`) restrict what types are acceptable, so you can safely access properties like `.length` or `.id`
  • 4The `keyof` constraint pattern (`K extends keyof T`) enables type-safe dynamic property access — this is one of the most useful patterns in TypeScript
  • 5Const type parameters (TypeScript 5.0+) infer literal types automatically, removing the need for `as const` at every call site
  • 6Generic base classes are the backbone of patterns like repositories, services, and state containers in real applications

Master typescript generics

Take the TypeScript Essentials course with hands-on lessons and challenges.

Examples

Basic generic function — type flows through

typescript

The type parameter T captures whatever type the array contains. TypeScript infers T from the argument — no need to write firstElement<number>([1, 2, 3]).

Generic API wrapper — a pattern you'll use constantly

typescript

This is the pattern most backend and frontend codebases use. The caller specifies the expected response shape, and TypeScript validates all property access downstream.

Constraints with extends — require minimum shape

typescript

The constraint T extends HasId guarantees every item has an id property, but the full type (with name and role) is preserved in the return type. The constraint is a floor, not a ceiling.

keyof constraint — type-safe property access

typescript

K extends keyof T constrains the key to valid property names. The return type T[K] resolves to the exact type of that property. This pattern is everywhere — lodash's _.get, form libraries, ORM select queries.

Const type parameters (TypeScript 5.0+)

typescript

The const modifier tells TypeScript to infer the narrowest possible type. Before TS 5.0, callers had to write 'as const' everywhere. Now the function author handles it once. Use this in builder functions, route definitions, and configuration factories.

Generic repository — real OOP pattern

typescript

Every ORM (Prisma, TypeORM, Drizzle) uses this pattern internally. The base class provides typed CRUD for any entity. Subclasses add entity-specific queries without duplicating the common operations.

Common Mistakes

Mistake:

Using `any` when a function works with multiple types — this throws away all type checking

Fix:

Replace `any` with a type parameter. If you write `function wrap(value: any): any`, change it to `function wrap<T>(value: T): T`. The type flows through and catches bugs.

Mistake:

Explicitly specifying type arguments when TypeScript can infer them — e.g. `firstElement<number>([1, 2, 3])`

Fix:

Let TypeScript infer: `firstElement([1, 2, 3])`. Only specify type arguments when inference fails or is ambiguous, like `fetchApi<User>("/api/users")` where the return type can't be inferred from the argument.

Mistake:

Accessing properties on an unconstrained type parameter — e.g. `value.length` when T could be anything

Fix:

Add a constraint: `<T extends { length: number }>`. This tells TypeScript what properties T is guaranteed to have, so the access is safe.

Mistake:

Over-constraining generics with properties the function body never uses

Fix:

Only constrain what you actually access. If the function doesn't use `.length`, don't require it. Unnecessary constraints reject valid inputs for no reason.

Best Practices

  • Let TypeScript infer type arguments by default — only specify them when inference fails or the result is ambiguous
  • Use descriptive names for multiple type parameters: `TKey`, `TValue`, `TItem` instead of `T`, `U`, `V`
  • Prefer generic interfaces over function overloads when the same function shape applies to multiple types
  • Use `keyof` constraints for any function that accesses dynamic properties — it's the most common and useful generic pattern
  • Add default type parameters (`T = SomeDefault`) when there's a sensible default — it simplifies the common case
  • Keep generic base classes thin — only put truly universal operations in the base, push domain-specific logic to subclasses

Summary

Generics are type parameters that make code reusable without sacrificing type safety. TypeScript infers them from usage in most cases. Constraints (`extends`) define the minimum shape a type must have, while `keyof` constraints enable type-safe property access. Const type parameters (TS 5.0+) infer literal types automatically. In practice, you'll use generics most often in API wrappers, utility functions, data structures, and base classes.

Practice TypeScript with hands-on challenges

Learn typescript generics hands-on in your IDE

Interactive lessons and challenges on Stanza, practice in VS Code, Cursor, or the web.

Related Concepts

Related Cheatsheets

Master TypeScript with Stanza

Interactive lessons and challenges, right in your code editor.

Check the free courses. No credit card.