JS Array Reduce
reduce recorre un array y lo transforma en un único resultado: un número, un objeto, otro array, un Map, una cadena, etc.
La idea principal es que en cada vuelta recibes un acumulador, lees el elemento actual y devuelves el acumulador actualizado.
const resultado = array.reduce((acc, item, index, arrayOriginal) => {
return acc;
}, valorInicial);Parámetros
acc: acumulador. Es el valor que se va construyendo.item: elemento actual del array.index: posición del elemento actual.arrayOriginal: array completo sobre el que se está iterando.valorInicial: valor inicial del acumulador.
Usar valorInicial casi siempre es lo más claro. Evita errores con arrays vacíos y deja explícito el tipo de resultado.
const total = [0, 1, 2, 3, 4, 5].reduce((acc, number) => {
return acc + number;
}, 0);
// 15En cada iteración:
0 + 1 = 1
1 + 2 = 3
3 + 3 = 6
6 + 4 = 10
10 + 5 = 15Cuándo usar reduce
reduce encaja cuando quieres calcular un valor nuevo a partir de todos los elementos y el resultado no es simplemente un map o un filter.
Buenas señales:
- Necesitas un acumulador.
- Quieres construir un objeto índice.
- Quieres agrupar elementos.
- Quieres contar frecuencias.
- Quieres calcular totales, máximos, mínimos o estadísticas.
Si solo quieres transformar cada elemento, usa map. Si solo quieres descartar elementos, usa filter. Si solo quieres ejecutar efectos, usa forEach o un for...of.
Sumar valores
Algoritmo típico para calcular totales: carrito, métricas, líneas de factura, votos, puntos, etc.
const cart = [
{ name: "Keyboard", price: 80, quantity: 1 },
{ name: "Mouse", price: 35, quantity: 2 },
{ name: "Monitor", price: 220, quantity: 1 },
];
const total = cart.reduce((acc, item) => {
return acc + item.price * item.quantity;
}, 0);
// 370Contar frecuencias
Algoritmo muy usado en entrevistas, analítica y normalización de datos: contar cuántas veces aparece cada valor.
const tags = ["js", "css", "js", "react", "css", "js"];
const frequencies = tags.reduce((acc, tag) => {
acc[tag] = (acc[tag] ?? 0) + 1;
return acc;
}, {});
// {
// js: 3,
// css: 2,
// react: 1
// }También sirve para detectar duplicados:
const numbers = [1, 2, 3, 2, 4, 1, 5];
const repeated = numbers.reduce((acc, number, index, array) => {
if (array.indexOf(number) !== index && !acc.includes(number)) {
acc.push(number);
}
return acc;
}, []);
// [2, 1]Agrupar por propiedad
Este patrón es un groupBy manual. Es útil para preparar datos para tablas, secciones, estadísticas o dashboards.
const orders = [
{ id: 1, status: "paid", total: 120 },
{ id: 2, status: "pending", total: 75 },
{ id: 3, status: "paid", total: 40 },
{ id: 4, status: "cancelled", total: 20 },
];
const ordersByStatus = orders.reduce((acc, order) => {
const statusOrders = acc[order.status] ?? [];
return {
...acc,
[order.status]: [...statusOrders, order],
};
}, {});
// {
// paid: [
// { id: 1, status: "paid", total: 120 },
// { id: 3, status: "paid", total: 40 }
// ],
// pending: [{ id: 2, status: "pending", total: 75 }],
// cancelled: [{ id: 4, status: "cancelled", total: 20 }]
// }Versión más eficiente si el array es grande:
const ordersByStatus = orders.reduce((acc, order) => {
acc[order.status] ??= [];
acc[order.status].push(order);
return acc;
}, {});La primera versión evita mutar el acumulador y puede ser más cómoda en código funcional. La segunda crea menos objetos y suele ser mejor para grandes volúmenes.
Indexar por id
Otro uso útil es convertir un array en un diccionario para hacer búsquedas rápidas.
const users = [
{ id: 1, name: "Ana" },
{ id: 2, name: "Luis" },
{ id: 3, name: "Marta" },
];
const usersById = users.reduce((acc, user) => {
acc[user.id] = user;
return acc;
}, {});
// {
// 1: { id: 1, name: "Ana" },
// 2: { id: 2, name: "Luis" },
// 3: { id: 3, name: "Marta" }
// }Esto permite pasar de una búsqueda lineal a una búsqueda directa:
const posts = [
{ id: 1, title: "Intro", createdBy: 2 },
{ id: 2, title: "Guía", createdBy: 1 },
];
const postsWithUser = posts.map((post) => ({
...post,
user: usersById[post.createdBy],
}));Sin índice tendrías que hacer users.find(...) por cada post. Con muchos usuarios y muchos posts, el índice evita repetir búsquedas.
Calcular máximos y mínimos
Patrón típico para rankings, precios, fechas o métricas.
const products = [
{ name: "Basic", price: 12 },
{ name: "Pro", price: 29 },
{ name: "Enterprise", price: 99 },
];
const mostExpensive = products.reduce((max, product) => {
return product.price > max.price ? product : max;
}, products[0]);
// { name: "Enterprise", price: 99 }Para arrays vacíos conviene controlar el caso antes:
const mostExpensive = products.length
? products.reduce((max, product) => (product.price > max.price ? product : max))
: null;Aplanar arrays
Aunque hoy existe flat, entender este patrón ayuda a reconocer reducciones que convierten arrays anidados en una lista.
const pages = [
["home", "about"],
["blog", "contact"],
["legal"],
];
const routes = pages.reduce((acc, pageRoutes) => {
return [...acc, ...pageRoutes];
}, []);
// ["home", "about", "blog", "contact", "legal"]Para este caso concreto, normalmente es mejor:
const routes = pages.flat();Crear un pipeline de funciones
reduce también puede encadenar funciones. Es útil cuando quieres aplicar transformaciones en orden.
const trim = (value) => value.trim();
const lowercase = (value) => value.toLowerCase();
const removeSpaces = (value) => value.replaceAll(" ", "-");
const normalizeSlug = [trim, lowercase, removeSpaces].reduce((value, fn) => {
return fn(value);
}, " Hola Mundo ");
// "hola-mundo"Errores comunes
Olvidar devolver el acumulador
const result = [1, 2, 3].reduce((acc, number) => {
acc.push(number * 2);
}, []);
// TypeError on the second iteration because the first one returns undefinedLa callback debe devolver el acumulador:
const result = [1, 2, 3].reduce((acc, number) => {
acc.push(number * 2);
return acc;
}, []);No usar valor inicial
[].reduce((acc, number) => acc + number);Esto lanza un error porque no existe primer elemento que usar como acumulador inicial.
[].reduce((acc, number) => acc + number, 0);Esto devuelve 0.
Regla práctica
Usa reduce cuando el nombre del acumulador sea claro. Si necesitas explicar demasiado qué hace, probablemente un for...of sea más legible.