What are generics in TypeScript and what are they for?
Show answer — try answering out loud first
Generics are type parameters: they let you write functions and structures that work with several types without losing the relationship between input and output. If you pass a string[] to primerElemento, the compiler knows it returns string, not a generic any.
Imagine a function that returns the first element of an array. Without generics you have two bad options: type it with any (you lose all safety) or write a version for each type (you duplicate code). Generics offer the third way:
<T>is a type parameter: like regular parameters, but for types. It gets "filled in" on each call, almost always by inference.- They preserve the input → output relationship:
function primero<T>(arr: T[]): Tpromises to return the same type the array contains. - Constraints with
extends:<T extends { id: number }>means "any type, as long as it has a numericid". Inside the function you can safely use.id. K extends keyof T: the star pattern.Kcan only be a real key ofT, so accessingobjeto[clave]is safe and the return is typed asT[K].
In React you use them without noticing: useState<number>(0) is a generic hook.
// Without generics the type is lost:
// function primerElemento(arr: any[]): any -> the output is any
// With generics, the input -> output relationship is preserved
function primerElemento<T>(arr: T[]): T | undefined {
return arr[0];
}
const numero = primerElemento([10, 20, 30]); // number | undefined
const palabra = primerElemento(["a", "b", "c"]); // string | undefined
// numero.toUpperCase();
// Error: Property 'toUpperCase' does not exist on type 'number'.
// Constraint with extends: T must have a length
function masLargo<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
masLargo("hola", "adiós"); // works: string has length
masLargo([1, 2], [1, 2, 3]); // works: arrays have length
// masLargo(10, 20);
// Error: Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.
// K extends keyof T: only accepts real keys of the object
function extraerPropiedad<T, K extends keyof T>(objeto: T, clave: K): T[K] {
return objeto[clave];
}
const usuario = { nombre: "Ana", edad: 30 };
const nombre = extraerPropiedad(usuario, "nombre"); // string
const edad = extraerPropiedad(usuario, "edad"); // number
// extraerPropiedad(usuario, "email");
// Error: Argument of type '"email"' is not assignable to parameter of type '"nombre" | "edad"'.
Using any where a generic belonged, "because hey, it works with everything":
// Compiles, but the output loses the type
function primeroMal(arr: any[]): any {
return arr[0];
}
const x = primeroMal([1, 2, 3]);
x.toUpperCase(); // compiles and blows up at runtime: x is any
// Fix: the generic preserves the type
function primeroBien<T>(arr: T[]): T | undefined {
return arr[0];
}
const y = primeroBien([1, 2, 3]); // number | undefined
The key difference: any accepts everything and forgets everything; the generic accepts everything and remembers the type.
"A generic is a type parameter: it lets me write a single function that works with many types without losing the relationship between input and output. For example,
primerElemento<T>(arr: T[]): Treturns number if I pass it numbers and string if I pass it strings, something I'd lose withany. I can constrain the generic withextends, and the pattern I use most isK extends keyof Tto access an object's properties safely. In React I use them daily:useState<T>is a generic hook."
Write ultimoElemento<T> that returns the last element of an array (or undefined if it's empty), and verify that with ["a", "b"] the result is typed as string | undefined.
See solution
function ultimoElemento<T>(arr: T[]): T | undefined {
return arr[arr.length - 1];
}
const letra = ultimoElemento(["a", "b"]); // string | undefined
const numero = ultimoElemento([1, 2, 3]); // number | undefined
const vacio = ultimoElemento<number>([]); // undefined at runtime
// The relationship is preserved: if letra exists, it's a string
if (letra !== undefined) {
console.log(letra.toUpperCase()); // "B"
}