TypeScript generics are one of the type system’s most powerful features — and one of the most frequently misunderstood. Many developers either avoid generics (writing `any` instead) or over-engineer with generics they do not need. This guide focuses on the practical patterns that appear in everyday TypeScript code and the specific problems they solve.
What Generics Are and When You Need Them
The core problem generics solve: writing functions that work across multiple types while preserving type information. Without generics:
“`typescript
function identity(value: any): any { return value; }
const result = identity(42); // result: any — type information lost
“`
With generics:
“`typescript
function identity
const result = identity(42); // result: number — type preserved
“`
The `T` is a type parameter — a placeholder for a type that will be specified (or inferred) at call time. When to use generics: when you need a function or class that behaves consistently across types while preserving type information; when you are building reusable utility types; when the return type of a function depends on the input type. When NOT to use generics: when `unknown` (type-checked but unknown) or a union type (e.g., `string | number`) would suffice; when the types are always the same. Over-generics is a common antipattern — if a function only ever takes and returns strings, it should not be generic. The most important generic patterns: generic functions (as above), generic interfaces, generic type aliases, and generic constraints.
Practical Generic Patterns
Generic constraints (`extends`): constrain what types a generic can be. The most common pattern:
“`typescript
function getProperty
return obj[key];
}
const user = { name: “Alice”, age: 30 };
const name = getProperty(user, “name”); // string — autocomplete works
// getProperty(user, “email”) would error at compile time
“`
This is the `keyof` pattern — TypeScript’s way of expressing “a string that is one of the keys of type T”. The return type `T[K]` is a “indexed access type” — TypeScript resolves it to the actual property type. Generic interfaces for data structures:
“`typescript
interface ApiResponse
data: T;
status: number;
message: string;
}
type UserResponse = ApiResponse
type PostsResponse = ApiResponse
“`
This pattern is ubiquitous in API client code. The `Awaited
“`typescript
type IsArray
type A = IsArray
type B = IsArray
“`
The `infer` keyword in conditional types:
“`typescript
type UnpackPromise
type Unpacked = UnpackPromise
type Direct = UnpackPromise
“`
The `infer U` says “if T is a Promise, infer the wrapped type into U”. This is how utility types like `Awaited
“`typescript
type Partial
type Readonly
type Stringify
“`
The built-in utility types (`Partial




