Generics: Reusable, Type-Safe Abstractions
Learn generic functions, constraints, defaults, keyof, and preserving relationships between input and output types.
Generic Functions and Type Parameters
Generics let a function or type operate on multiple types while preserving relationships between them. A type parameter such as T is a placeholder chosen by inference or explicitly supplied by a caller. Unlike any, a generic preserves information: if a function receives a T, its result can remain T.
Introduce generics when the same logic works for multiple types and the relationship matters. Do not make every helper generic without a reason; unnecessary type parameters can make APIs harder to understand.
function identity<T>(value: T): T {
return value;
}
const textValue = identity("hello"); // string
const countValue = identity(42); // numberT because it is inferred.- Generics preserve relationships between values and types.
- Inference often supplies generic arguments.
- Prefer useful type relationships over generic-looking code.
Why is `identity<T>(value: T): T` safer than using `any`?
Generic Constraints and keyof
A generic constraint limits which types may be used for a type parameter. T extends { id: string } means T must have at least that shape while retaining any additional properties. The keyof operator produces a union of property keys, and K extends keyof T ensures that a key is valid for the given object type.
This pattern is useful for property access helpers and reusable data utilities. The relationship between T and K is important: it allows the result type to be T[K], the actual type of the selected property.
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const product = { id: "p-1", price: 99, active: true };
const price = getProperty(product, "price"); // number
// getProperty(product, "missing"); // ErrorK must be one of the keys of T, and the return type depends on the selected key. This prevents misspelled property names and retains the property's value type.- Use
extendsto constrain generic inputs. keyof Trepresents valid keys ofT.T[K]preserves the selected property's type.
What does `K extends keyof T` guarantee?
Generic Defaults and API Design
Generic parameters can have defaults, which callers use when they do not provide an explicit type argument. Defaults should be sensible and should not hide uncertainty. For API response wrappers, a generic payload type is often useful; an error type can have a default if the library has a consistent convention.
Good generic APIs make common cases easy and unusual cases possible. Keep the number of type parameters small, order required parameters before defaulted ones, and avoid exposing internal type complexity when a simpler public contract would work.
type ApiResponse<TData, TError = string> =
| { ok: true; data: TData }
| { ok: false; error: TError };
const success: ApiResponse<{ id: string }> = {
ok: true,
data: { id: "u-1" }
};TData is inferred or supplied for each response. TError defaults to string, but callers can provide a structured error type when needed.- Generic defaults reduce repetitive type arguments.
- Keep public generic APIs understandable.
- Model success and failure as a discriminated union when their shapes differ.
When is a generic default used?