Cómo iterar millones de registros en JavaScript sin morir en el intento

1 min de lectura

Estrategias reales para recorrer grandes volúmenes de datos en JavaScript optimizando rendimiento, memoria y tiempos de ejecución.

  • JavaScript
  • Performance
  • Backend
  • Optimización
  • Big Data

Cuando trabajas con grandes volúmenes de datos en JavaScript (cientos de miles o millones de registros), iterar de forma incorrecta puede romper tu aplicación: consumo excesivo de memoria, bloqueos del event loop y tiempos de respuesta inaceptables.

En este artículo vamos directo a lo importante: cómo iterar millones de registros de forma eficiente.

El problema real

Un ejemplo típico:

const data = new Array(1_000_000).fill(0);

data.forEach(item => {
  // procesamiento pesado
});

Esto parece inocente, pero tiene varios problemas:

  • Bloquea el hilo principal
  • No permite pausas (event loop bloqueado)
  • Puede consumir mucha memoria
  • No es controlable

Métodos de iteración y su impacto

for clásico (el más rápido)

for (let i = 0; i < data.length; i++) {
  const item = data[i];
}

Ventajas:

  • Más rápido
  • Menor overhead
  • Control total del índice

Ideal para procesamiento intensivo.

for...of

for (const item of data) {
}

Ventajas:

  • Más limpio
  • Legible

Desventaja:

  • Ligeramente más lento que el for tradicional

forEach (evitar en grandes volúmenes)

data.forEach(item => {});

Problemas:

  • No puedes usar break o continue
  • Menor rendimiento
  • Difícil de controlar

Procesamiento por bloques (la clave)

La mejor estrategia para millones de registros es dividir el trabajo.

const chunkSize = 1000;

for (let i = 0; i < data.length; i += chunkSize) {
  const chunk = data.slice(i, i + chunkSize);

  chunk.forEach(item => {
    // procesar
  });
}

Ventajas:

  • Controlas memoria
  • Evitas bloqueos largos
  • Más escalable

Liberar el event loop (muy importante)

Si estás en frontend o Node.js, necesitas evitar bloquear el hilo:

const processChunk = async (data, chunkSize = 1000) => {
  for (let i = 0; i < data.length; i += chunkSize) {
    const chunk = data.slice(i, i + chunkSize);

    for (let j = 0; j < chunk.length; j++) {
      // procesar
    }

    await new Promise(resolve => setTimeout(resolve, 0));
  }
};

Esto permite que el sistema respire.

Streams (nivel pro)

Para datos realmente grandes (archivos, DB, etc):

const fs = require('fs');
const readline = require('readline');

const stream = fs.createReadStream('file.txt');

const rl = readline.createInterface({
  input: stream,
});

rl.on('line', (line) => {
  // procesar línea por línea
});

Ventajas:

  • No cargas todo en memoria
  • Escalable
  • Ideal para millones de registros

Paralelismo (cuando aplica)

const promises = data.map(item => asyncProcess(item));

await Promise.all(promises);

Cuidado:

  • Puede saturar CPU/memoria
  • Mejor usar límites de concurrencia

Ejemplo controlado:

import pLimit from 'p-limit';

const limit = pLimit(10);

await Promise.all(
  data.map(item => limit(() => asyncProcess(item)))
);

Recomendaciones prácticas

Usa esto

  • for clásico para rendimiento máximo
  • procesamiento por chunks
  • streams para archivos grandes
  • control de concurrencia

Evita esto

  • forEach en datasets enormes
  • cargar todo en memoria sin necesidad
  • Promise.all sin límites

Conclusión

Iterar millones de registros en JavaScript no es solo recorrer arrays, es un problema de arquitectura.

La diferencia entre una app que escala y una que colapsa está en:

  • Cómo iteras
  • Cuánta memoria usas
  • Cómo manejas el event loop

Si trabajas con grandes volúmenes de datos, dominar estas técnicas no es opcional.


Optimizar iteraciones no es micro-optimización, es supervivencia en producción.