Write reusable functions and types that keep full type precision on every call.
Open this lesson in KodokonYou already use generics without realizing it: string[], Promise<number>... Time to write your own. A generic introduces a type parameter, a variable replaced by a concrete type at each use. The goal: write the logic once, without sacrificing type precision.
function first<T>(items: T[]): T | undefined {
return items[0];
}
const n = first([10, 20, 30]);
const s = first(["a", "b"]);Notice that you didn't specify anything at the call site: TypeScript infers T from the argument. An unconstrained generic accepts anything; yet a function often needs a minimum of guarantees about its data. The extends keyword imposes a minimal shape on T.
interface HasId {
id: number;
}
function findById<T extends HasId>(
items: T[],
id: number
): T | undefined {
return items.find((item) => item.id === id);
}Types themselves can be generic. This is everyday life in network code: the response structure is stable, only the content of data varies. A single type then describes all your API responses.
interface ApiResponse<T> {
ok: boolean;
data: T;
}
type UserResponse = ApiResponse<{ name: string }>;
const res: UserResponse = {
ok: true,
data: { name: "Ada" },
};