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);
 
// 15

En cada iteración:

0 + 1 = 1
1 + 2 = 3
3 + 3 = 6
6 + 4 = 10
10 + 5 = 15

Cuá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);
 
// 370

Contar 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 undefined

La 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.