Sintaxis genérica y el patrón identity
Los tipos genéricos permiten crear componentes reutilizables que trabajan con distintos tipos de datos sin perder la seguridad de tipos. La sintaxis genérica se basa en los corchetes angulares <> y un parámetro de tipo, convencionalmente representado con la letra T.

Sin genéricos, una función que devuelve el mismo valor que recibe necesita un tipo concreto o recurrir a any:
function identityNumber(arg: number): number {
return arg
}
function identityAny(arg: any): any {
return arg
}
La versión con any acepta cualquier tipo, pero pierde la información sobre qué tipo se pasó. Los genéricos resuelven este problema capturando el tipo del argumento en un parámetro de tipo:
function identity<T>(arg: T): T {
return arg
}
El parámetro de tipo
Tactúa como una variable que opera sobre tipos en lugar de valores. Captura el tipo proporcionado y lo utiliza tanto en el parámetro como en el retorno.
El resultado es una función que acepta cualquier tipo y devuelve exactamente ese mismo tipo, manteniendo la trazabilidad completa del tipo a lo largo de la operación.
Inferencia de parámetros de tipo
TypeScript puede inferir automáticamente el tipo genérico a partir del argumento proporcionado. Existen dos formas de invocar una función genérica:
function identity<T>(arg: T): T {
return arg
}
// Forma explícita: se indica el tipo entre <>
let resultado1 = identity<string>("hola")
// Forma implícita: TypeScript infiere T como string
let resultado2 = identity("hola")
// TypeScript infiere T como number
let resultado3 = identity(42)
// TypeScript infiere T como boolean
let resultado4 = identity(true)
La inferencia de tipos es la forma preferida porque produce código más limpio. Solo es necesario especificar el tipo explícitamente cuando TypeScript no puede inferirlo correctamente, algo que ocurre en escenarios más complejos:
function crearPar<T, U>(a: T, b: U): [T, U] {
return [a, b]
}
// Inferencia correcta: [string, number]
let par1 = crearPar("edad", 30)
// Especificación explícita cuando se necesita un tipo más amplio
let par2 = crearPar<string, string | number>("clave", 42)
La inferencia de tipos genéricos funciona analizando los argumentos pasados a la función. Cuando el compilador puede determinar el tipo sin ambigüedad, la especificación explícita es innecesaria.
Funciones genéricas
Las funciones genéricas son la aplicación más directa de los genéricos. Permiten escribir operaciones que funcionan con cualquier tipo manteniendo la coherencia de tipos en entrada y salida.
Funciones que operan sobre arrays
Los genéricos son especialmente útiles para funciones que trabajan con colecciones:
function primerElemento<T>(array: T[]): T | undefined {
return array.length > 0 ? array[0] : undefined
}
const num = primerElemento([10, 20, 30]) // number | undefined
const texto = primerElemento(["a", "b", "c"]) // string | undefined
const vacío = primerElemento([]) // undefined
function invertirArray<T>(items: T[]): T[] {
return [...items].reverse()
}
const números = invertirArray([1, 2, 3]) // number[]
const nombres = invertirArray(["Ana", "Luis"]) // string[]
Funciones con múltiples parámetros de tipo
Cuando una función necesita relacionar tipos distintos, se pueden declarar varios parámetros de tipo:
function transformar<T, U>(items: T[], fn: (item: T) => U): U[] {
return items.map(fn)
}
const longitudes = transformar(["hola", "mundo"], s => s.length) // number[]
const textos = transformar([1, 2, 3], n => n.toString()) // string[]
function combinar<T, U>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 }
}
const resultado = combinar(
{ nombre: "Elena" },
{ edad: 28 }
)
// tipo: { nombre: string } & { edad: number }
console.log(resultado.nombre) // "Elena"
console.log(resultado.edad) // 28
Funciones flecha genéricas
Las arrow functions también admiten parámetros de tipo. En archivos .tsx puede ser necesario añadir una coma tras el parámetro para evitar ambigüedad con etiquetas JSX:
const envolver = <T>(valor: T): T[] => {
return [valor]
}
const nums = envolver(5) // number[]
const strs = envolver("hola") // string[]
// En archivos .tsx se usa la coma:
const envolverTsx = <T,>(valor: T): T[] => [valor]
Funciones de orden superior genéricas
Los genéricos permiten crear funciones que aceptan o devuelven otras funciones de forma tipada:
function ejecutar<T, U>(dato: T, operación: (x: T) => U): U {
return operación(dato)
}
const longitud = ejecutar("TypeScript", x => x.length) // number
const doble = ejecutar(5, x => x * 2) // number
function componer<A, B, C>(
f: (b: B) => C,
g: (a: A) => B
): (a: A) => C {
return (a) => f(g(a))
}
const parsearYDuplicar = componer(
(n: number) => n * 2,
(s: string) => parseInt(s, 10)
)
console.log(parsearYDuplicar("15")) // 30
Interfaces genéricas
Las interfaces genéricas definen contratos flexibles que pueden adaptarse a distintos tipos de datos. El parámetro de tipo se declara junto al nombre de la interfaz:
interface Caja<T> {
contenido: T
}
let cajaTexto: Caja<string> = { contenido: "TypeScript" }
let cajaNumero: Caja<number> = { contenido: 42 }
Las interfaces genéricas pueden definir métodos que utilicen el parámetro de tipo:
interface Coleccion<T> {
elementos: T[]
agregar(item: T): void
obtener(indice: number): T | undefined
filtrar(predicado: (item: T) => boolean): T[]
}
function crearColeccion<T>(): Coleccion<T> {
const elementos: T[] = []
return {
elementos,
agregar(item: T) {
elementos.push(item)
},
obtener(indice: number) {
return elementos[indice]
},
filtrar(predicado: (item: T) => boolean) {
return elementos.filter(predicado)
}
}
}
const números = crearColeccion<number>()
números.agregar(10)
números.agregar(20)
números.agregar(30)
const mayoresA15 = números.filtrar(n => n > 15) // [20, 30]
Una interfaz genérica con múltiples parámetros permite modelar relaciones entre tipos:
interface Par<K, V> {
clave: K
valor: V
}
const entrada: Par<string, number> = { clave: "edad", valor: 30 }
const config: Par<string, boolean> = { clave: "activo", valor: true }
interface Resultado<T, E> {
exito: boolean
datos?: T
error?: E
}
function dividir(a: number, b: number): Resultado<number, string> {
if (b === 0) {
return { exito: false, error: "Division por cero" }
}
return { exito: true, datos: a / b }
}
const r = dividir(10, 2)
if (r.exito) {
console.log(r.datos) // 5
}
Alias de tipo genéricos
Los alias de tipo (type) también aceptan parámetros genéricos, lo que permite crear tipos reutilizables con una sintaxis concisa:
type Respuesta<T> = {
datos: T
estado: number
mensaje: string
}
const respuestaTexto: Respuesta<string> = {
datos: "OK",
estado: 200,
mensaje: "Operación exitosa"
}
interface Usuario {
id: number
nombre: string
}
const respuestaUsuario: Respuesta<Usuario> = {
datos: { id: 1, nombre: "Ana" },
estado: 200,
mensaje: "Usuario encontrado"
}
Los alias genéricos son especialmente expresivos con tipos union discriminados:
type ResultadoOperacion<T, E = string> =
| { tipo: "exito"; valor: T }
| { tipo: "error"; error: E }
function parsearJSON<T>(texto: string): ResultadoOperacion<T> {
try {
const datos = JSON.parse(texto) as T
return { tipo: "exito", valor: datos }
} catch (e) {
return { tipo: "error", error: "JSON inválido" }
}
}
const resultado = parsearJSON<{ nombre: string }>('{"nombre": "Luis"}')
if (resultado.tipo === "exito") {
console.log(resultado.valor.nombre) // "Luis"
}
También se pueden crear alias genéricos para funciones:
type Transformador<T, U> = (entrada: T) => U
const aTexto: Transformador<number, string> = n => n.toString()
const aNumero: Transformador<string, number> = s => parseInt(s, 10)
console.log(aTexto(42)) // "42"
console.log(aNumero("100")) // 100
Parámetros de tipo por defecto
TypeScript permite asignar valores por defecto a los parámetros de tipo, de forma similar a los valores por defecto en parámetros de funciones:
interface RespuestaAPI<T = any> {
datos: T
estado: number
}
// Sin especificar tipo: T es any
const genérico: RespuestaAPI = {
datos: { lo_que_sea: true },
estado: 200
}
// Especificando tipo concreto
const tipado: RespuestaAPI<string[]> = {
datos: ["a", "b", "c"],
estado: 200
}
Los valores por defecto son especialmente útiles cuando un tipo genérico tiene múltiples parámetros y algunos tienen un valor habitual:
type Evento<TDatos = void, TMeta = Record<string, unknown>> = {
tipo: string
datos: TDatos
meta: TMeta
timestamp: number
}
// Solo especificamos el primer parámetro
const eventoSimple: Evento = {
tipo: "click",
datos: undefined,
meta: {},
timestamp: Date.now()
}
// Especificamos ambos parámetros
const eventoCompleto: Evento<{ x: number; y: number }, { fuente: string }> = {
tipo: "click",
datos: { x: 100, y: 200 },
meta: { fuente: "raton" },
timestamp: Date.now()
}
Los parámetros con valor por defecto deben aparecer después de los parámetros sin valor por defecto, igual que en las funciones regulares.
type Cache<TValor, TClave = string> = {
obtener(clave: TClave): TValor | undefined
establecer(clave: TClave, valor: TValor): void
}
// TClave usa el valor por defecto string
const cacheNumeros: Cache<number> = {
obtener(clave: string) { return undefined },
establecer(clave: string, valor: number) { }
}
// TClave se específica como number
const cacheIndexado: Cache<string, number> = {
obtener(clave: number) { return undefined },
establecer(clave: number, valor: string) { }
}
Trabajar con arrays de forma genérica
Los arrays son una de las estructuras donde los genéricos brillan con mayor claridad. TypeScript proporciona el tipo Array<T> como alias del tipo T[], y ambas notaciones son equivalentes:
let numeros1: Array<number> = [1, 2, 3]
let numeros2: number[] = [1, 2, 3] // equivalente
Crear funciones genéricas que operen sobre arrays permite reutilizar lógica para cualquier tipo de elemento:
function ultimoElemento<T>(arr: T[]): T | undefined {
return arr[arr.length - 1]
}
function agruparDe<T>(arr: T[], tamano: number): T[][] {
const grupos: T[][] = []
for (let i = 0; i < arr.length; i += tamano) {
grupos.push(arr.slice(i, i + tamano))
}
return grupos
}
console.log(ultimoElemento([10, 20, 30])) // 30
console.log(ultimoElemento(["a", "b"])) // "b"
console.log(agruparDe([1, 2, 3, 4, 5], 2)) // [[1, 2], [3, 4], [5]]
console.log(agruparDe(["a", "b", "c", "d"], 3)) // [["a", "b", "c"], ["d"]]
Las funciones genéricas sobre arrays pueden combinar búsqueda, filtrado y transformación:
function buscar<T>(
arr: T[],
predicado: (item: T) => boolean
): T | undefined {
for (const item of arr) {
if (predicado(item)) return item
}
return undefined
}
interface Producto {
id: number
nombre: string
precio: number
}
const productos: Producto[] = [
{ id: 1, nombre: "Teclado", precio: 50 },
{ id: 2, nombre: "Raton", precio: 25 },
{ id: 3, nombre: "Monitor", precio: 300 }
]
const caro = buscar(productos, p => p.precio > 100)
console.log(caro?.nombre) // "Monitor"
function eliminarDuplicados<T>(arr: T[]): T[] {
return [...new Set(arr)]
}
const unicos = eliminarDuplicados([1, 2, 2, 3, 3, 3])
console.log(unicos) // [1, 2, 3]
const unicosTexto = eliminarDuplicados(["a", "b", "a"])
console.log(unicosTexto) // ["a", "b"]
Convenciones de nomenclatura
Las convenciones de nombres para parámetros de tipo ayudan a que el código genérico sea más legible:
// T: tipo general
function procesar<T>(valor: T): T { return valor }
// K, V: clave y valor (colecciones tipo mapa)
type Mapa<K extends string, V> = Record<K, V>
// E: elemento (colecciones)
function primero<E>(arr: E[]): E | undefined { return arr[0] }
// TInput, TOutput: cuando hay transformación
type Convertidor<TInput, TOutput> = (entrada: TInput) => TOutput
// TDatos, TError: tipos de dominio descriptivos
type Respuesta<TDatos, TError = Error> = {
datos?: TDatos
error?: TError
}
Usa nombres de una sola letra (
T,U,K,V) para genéricos simples. Usa nombres descriptivos con prefijoT(TInput,TOutput,TDatos) cuando hay más de dos parámetros o cuando el contexto requiere claridad adicional.
Fuentes y referencias
Documentación oficial y recursos externos para profundizar en TypeScript
Documentación oficial de TypeScript
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, TypeScript es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de TypeScript
Explora más contenido relacionado con TypeScript y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Comprender la sintaxis genérica