Programming Guides
TypeScript Generics, Explained Without the Jargon
Generics look intimidating because of the angle brackets. Underneath, they are a simple idea: 'a placeholder for a type I will tell you later.'

Generics intimidate new TypeScript developers more than any other feature. The angle brackets, the <T,> syntax, the compiler errors that span six lines — none of it helps. But the underlying idea is genuinely simple, and once it clicks, most of the syntax stops being scary.
Here is the idea, in one sentence: a generic is a placeholder for a type you promise to fill in later.
The problem generics solve
Suppose you want a function that returns the first element of an array. A first attempt:
function first(arr: any[]): any {
return arr[0];
}This works, but it lies. The return type any throws away everything TypeScript knows. If you pass in an array of strings, you get back any, and the compiler will happily let you call .toFixed() on it.
We want to say: "whatever type is in the array, that same type comes out." That is exactly a placeholder.
function first<T>(arr: T[]): T {
return arr[0];
}Read <T> as: "there is some type I will call T; I do not know what it is yet." When you call the function, TypeScript fills T in:
first([1, 2, 3]); // T is number, returns number
first(["a", "b"]); // T is string, returns stringThe caller decides T, and the function's signature stays honest.
The constraint that makes generics useful
Unconstrained generics are powerful but loose. Often you want to say: "T can be anything, but it must have a certain property." Enter extends:
function getLength<T extends { length: number }>(x: T): number {
return x.length;
}
getLength("hello"); // OK — strings have .length
getLength([1, 2, 3]); // OK — arrays have .length
getLength(42); // Error — numbers have no .lengthextends here does not mean inheritance in the OOP sense. It means "must be assignable to." T is still chosen by the caller, but only from types that have a length.
Two generics at once
Real signatures often relate two types. A map-like helper:
function transform<T, U>(arr: T[], fn: (item: T) => U): U[] {
return arr.map(fn);
}
transform([1, 2, 3], n => n.toString()); // T=number, U=string -> string[]The relationship "the function takes T and returns U, and the output array is U[]" is enforced by the signature. Get any of it wrong and the compiler tells you, before runtime.
The one trap to avoid
Generics that appear only in the return type defeat inference. This is the classic "no, I really want you to infer" mistake:
function parse<T>(json: string): T {
return JSON.parse(json);
}
const x = parse<{ id: string }>("..."); // you must specify THere T is not used in any parameter, so TypeScript cannot infer it — you must supply it explicitly, and the cast inside parse is unchecked. This is not generics being broken; it is generics honestly telling you "I have no information to fill this in." When you see a generic that only appears in the return type, treat it as a cast with extra steps.
Takeaways
- A generic is a placeholder the caller fills in.
extendsconstrains the placeholder, it does not mean subclassing.- Relate multiple generics to express real invariants.
- If a generic only appears in the return type, it is a cast — use it knowingly.
Generics are not advanced. They are variables, for types. Once you read them that way, the angle brackets become punctuation, not a warning sign.