Stop copy-pasting generic patterns you don't understand. Here's how to reason about TypeScript generics from first principles, with real-world examples from production code.
Generics in TypeScript are one of those concepts where most tutorials show you the what without explaining the why. Let me fix that.
The mental model I use: a generic is a function for types. Just as a function takes a value and returns a value, a generic takes a type and returns a type. Once that clicks, everything else follows naturally.
Consider a simple identity function. Without generics you'd write separate versions for string, number, boolean. With generics, you write it once: function identity<T>(arg: T): T { return arg; }. The T is a type parameter — a placeholder that gets filled in at call time.
Where it gets powerful is with constraints. You can say "I don't care what type this is, as long as it has a .length property" using extends: function longest<T extends { length: number }>(a: T, b: T): T. This lets TypeScript catch errors at the call site rather than inside the function.
The real-world pattern I use most: generic repository interfaces. A single Repository<T> interface that works with any entity — User, Post, Product. The database layer becomes completely decoupled from the business domain, and swapping implementations (Postgres → MongoDB) requires zero changes upstream.
The key insight is that generics are not about making code shorter. They're about making relationships between types explicit — and letting the compiler enforce those relationships for you.