"One signature, every type, no duplication."
Single type parameter
The simplest generic function takes one type parameter and uses it to relate inputs and outputs. function head<T>(arr: T[]): T | undefined — the return is either an element of the array (same type T) or undefined (empty array). The relationship between the input element type and the return is captured by sharing T.
Multiple type parameters
Real functions often relate two or more types. function map<T, U>(arr: T[], fn: (x: T) => U): U[] — read this as: take an array of T, and a function that maps T to U, return an array of U. The two parameters can be different types, and the relationship is between input and output of the callback.
Convention: name type parameters with single uppercase letters (T, U, V) when they're abstract, or with meaningful names (TInput, TResult) when the role helps readability. Both styles are common; pick one and be consistent.
Inference at call sites
TypeScript's inference for generic functions is excellent. For most calls, you never write <T> explicitly — the compiler figures it out from the arguments. The exception: when there's no argument to infer from (like calling a generic that only returns), or when inference picks too wide a type for your taste.
// Inferred:
const lengths = map([1, 2, 3], (n) => n.toString().length); // U inferred as number
// Explicit (rare):
const empty = identity<string>(/* you'd need an arg */);
unknown. Test by removing the generic and seeing if the relationship still holds.