"A generic is a type that hasn't decided yet. The caller fills in the slot."
The problem generics solve
Imagine you're writing a function first that returns the first element of an array. Without generics, you'd write one per element type — firstString, firstNumber, firstUser — or you'd accept any[] and return any, losing all type safety. Both options are bad.
Generics solve this by letting you parameterize the function over an unknown type, captured at the call site. function first<T>(arr: T[]): T — one function, one signature. The compiler figures out what T is from the argument and threads it through to the return type.
The slot metaphor
Think of <T> as a slot in the signature. The caller (or the compiler, via inference) fills the slot when the function is invoked. first([1, 2, 3]) fills T with number; first(['a', 'b']) fills T with string. The same function code runs in both cases; the type system tracks the difference.
This is what Array<T> is: an array type with a slot for the element type. Array<string> is string[]. Array<{ id: number }> is an array of objects. The same shape, different fills.
The relationship-preservation property
The killer feature of generics is that the slot is the same throughout the signature. If T appears in both a parameter and a return, the call enforces they're the same type. function identity<T>(x: T): T — whatever you put in is exactly what comes back, with the type preserved. This relationship-tracking is impossible with primitive types alone.