This content is only available in Portuguese.
Not translated yet for this language.
forEach esconde a intenção do código. Os outros métodos não.
forEach é genérico demais. Métodos como map, filter e reduce declaram a intenção do código antes do corpo, facilitando a leitura e manutenção. Saiba quando usar cada um.

forEach não é errado. É genérico demais.
Quando alguém lê um loop forEach, precisa ler o corpo inteiro antes de entender o que ele faz. Transforma dados? Filtra resultados? Procura um item? Valida uma condição? Impossível saber pelo nome do método.
map, filter, find, some, every, reduce — cada um declara a intenção antes do corpo. O nome do método já é documentação.
Esse é o argumento real do functional programming em JavaScript. Não performance, não elegância. Clareza de intenção.
map() — transformação sem modificar o original
forEach com push é o padrão mais comum pra transformar arrays. Funciona. Mas mistura a iteração com a acumulação, e o leitor precisa percorrer tudo antes de entender que o resultado é uma lista transformada.
// ❌ intenção invisível até a última linha
const nomes = [];
usuarios.forEach(usuario => {
if (usuario.ativo) {
nomes.push(usuario.nome.toUpperCase());
}
});
// ✅ intenção declarada pelo nome do método
const nomes = usuarios
.filter(usuario => usuario.ativo)
.map(usuario => usuario.nome.toUpperCase());
A versão com filter + map diz o que faz antes de mostrar como faz. Cada método tem uma responsabilidade: filter seleciona, map transforma. Separados, cada um é trivial de testar.
Um detalhe importante: map sempre retorna um array do mesmo tamanho. Se você quer tanto filtrar quanto transformar, filter vem antes.
filter() — seleção com critério explícito
forEach com condicional e push acumula elementos que passam em uma condição. O problema é que a condição de seleção fica misturada com a lógica de acumulação.
// ❌ o critério de seleção está escondido dentro do corpo
const resultado = [];
transacoes.forEach(t => {
if (t.tipo === 'credito' && t.valor > 100) {
resultado.push(t);
}
});
// ✅ o critério de seleção está visível na assinatura
const transacoesGrandes = transacoes
.filter(t => t.tipo === 'credito' && t.valor > 100);
filter recebe um predicado — uma função que retorna true ou false. Extrair o predicado como função nomeada torna o código ainda mais legível:
const ehCreditoGrande = t => t.tipo === 'credito' && t.valor > 100;
const transacoesGrandes = transacoes.filter(ehCreditoGrande);
Agora o critério tem nome, pode ser reutilizado, e pode ser testado isoladamente.
reduce() — uma passada, múltiplas computações
reduce é o método mais versátil do array, e o mais incompreendido. A resistência geralmente vem da sintaxe — o acumulador inicial parece mágica na primeira vez.
O caso de uso mais claro: quando você precisa calcular várias coisas sobre o mesmo array em uma passada só.
// ❌ três loops separados pra três cálculos diferentes
let totalCredito = 0;
let totalDebito = 0;
let porCategoria = {};
transacoes.forEach(t => {
if (t.tipo === 'credito') totalCredito += t.valor;
});
transacoes.forEach(t => {
if (t.tipo === 'debito') totalDebito += t.valor;
});
transacoes.forEach(t => {
porCategoria[t.categoria] = (porCategoria[t.categoria] || 0) + t.valor;
});
// ✅ um reduce, três cálculos simultâneos
const resumo = transacoes.reduce((acc, t) => {
// acumula por tipo
acc[t.tipo] = (acc[t.tipo] || 0) + t.valor;
// acumula por categoria
acc.porCategoria[t.categoria] =
(acc.porCategoria[t.categoria] || 0) + t.valor;
// saldo corrente
acc.saldo += t.tipo === 'credito' ? t.valor : -t.valor;
return acc;
}, { credito: 0, debito: 0, porCategoria: {}, saldo: 0 });
reduce também serve pra transformar arrays em objetos — algo que map e filter não fazem:
// transforma array em mapa indexado por id
const usuariosPorId = usuarios.reduce((acc, usuario) => {
acc[usuario.id] = usuario;
return acc;
}, {});
// acesso direto: usuariosPorId[42] em vez de .find(u => u.id === 42)
Uma ressalva honesta: reduce com lógica complexa dentro do callback vira difícil de ler. Se o acumulador começa a acumular mais de três coisas diferentes, vale considerar separar em etapas.
flatMap() — transformação + achatamento
flatMap resolve um problema específico: quando cada item do array produz uma lista, e você quer uma lista única no final.
const usuarios = [
{ nome: 'Ana', habilidades: ['React', 'Node', 'GraphQL'] },
{ nome: 'Carlos', habilidades: ['Vue', 'Python'] },
{ nome: 'Marina', habilidades: ['Angular', 'TypeScript'] }
];
// ❌ map retorna array de arrays
const errado = usuarios.map(u => u.habilidades);
// [['React', 'Node', 'GraphQL'], ['Vue', 'Python'], ['Angular', 'TypeScript']]
// com .flat() depois resolve, mas são duas passadas
const manual = usuarios.map(u => u.habilidades).flat();
// ✅ flatMap faz as duas em uma só
const todasHabilidades = usuarios.flatMap(u => u.habilidades);
// ['React', 'Node', 'GraphQL', 'Vue', 'Python', 'Angular', 'TypeScript']
flatMap também é útil pra filtrar e transformar ao mesmo tempo — retornar [] do callback é equivalente a remover o item:
// transforma e filtra em uma passada: retorna [] pra excluir
const creditos = transacoes.flatMap(t =>
t.tipo === 'credito' ? [t.valor] : []
);
some() e every() — validação com early termination
forEach pra validação exige variáveis de controle externas e percorre o array inteiro mesmo quando a resposta já é conhecida.
// ❌ percorre o array inteiro mesmo que o primeiro item já seja inválido
let todosValidos = true;
usuarios.forEach(u => {
if (!u.email || !u.email.includes('@')) {
todosValidos = false;
}
});
// ✅ para na primeira falha — O(1) no melhor caso
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const todosEmailsValidos = usuarios.every(u => emailRegex.test(u.email));
const algumAdmin = usuarios.some(u => u.papel === 'admin');
every para no primeiro false. some para no primeiro true. Esse comportamento — short-circuit evaluation — não é documentação de marketing, é como os métodos funcionam na spec do JavaScript.
A vantagem sobre forEach é direta: você não precisa de variável externa, não precisa de flag, e o método já comunica a intenção. every significa "todos devem passar". some significa "pelo menos um deve passar".
find() e findIndex() — busca com early termination
O padrão de busca com forEach tem um problema clássico: precisa verificar se o item já foi encontrado pra não sobrescrever.
// ❌ continua percorrendo mesmo depois de encontrar
let encontrado = null;
usuarios.forEach(u => {
if (u.id === idBuscado && !encontrado) {
encontrado = u;
}
});
// ✅ para assim que encontra
const usuario = usuarios.find(u => u.id === idBuscado);
const indice = usuarios.findIndex(u => u.id === idBuscado);
find retorna o item ou undefined. findIndex retorna o índice ou -1. Os dois param na primeira ocorrência.
Um padrão útil: find com fallback via operador || ou ??:
const usuario = usuarios.find(u => u.id === id) ?? { nome: 'Convidado', papel: 'guest' };
findLast e findLastIndex existem no ES2023 — buscam do fim pro início. Úteis quando a última ocorrência é mais relevante que a primeira.
Composição de funções — combinando operações
Quando várias transformações precisam ser aplicadas em sequência, function composition torna a intenção legível mesmo antes de ler o código.
// utilitário: pipe aplica funções da esquerda pra direita
const pipe = (...fns) => valor => fns.reduce((acc, fn) => fn(acc), valor);
// funções reutilizáveis com assinatura (predicate/transform) => array => resultado
const filtrar = predicado => arr => arr.filter(predicado);
const transformar = fn => arr => arr.map(fn);
const ordenar = comparar => arr => [...arr].sort(comparar);
const pegar = n => arr => arr.slice(0, n);
Com isso, um pipeline de processamento fica legível como uma lista de etapas:
const processarTransacoes = pipe(
filtrar(t => t.tipo === 'debito'),
ordenar((a, b) => b.valor - a.valor),
pegar(5),
transformar(t => ({ ...t, valorFormatado: `R$ ${t.valor.toFixed(2)}` }))
);
const top5Despesas = processarTransacoes(transacoes);
Cada função do pipeline é testável isoladamente. O pipeline em si descreve o processo em linguagem natural.
Isso não é obrigatório — chaining direto funciona bem em muitos casos. Composição faz mais sentido quando você precisa reutilizar pipelines ou quando o número de etapas cresce.
Quando forEach ainda faz sentido
Nem toda iteração precisa retornar algo. forEach é a escolha certa quando o objetivo é um side effect — atualizar o DOM, enviar eventos, logar, disparar requisições:
// forEach faz sentido aqui: side effect intencional
botoes.forEach(botao => {
botao.addEventListener('click', handleClick);
});
Usar map nesse caso seria errado — map implica que você vai usar o array retornado, e aqui o retorno seria descartado.
A distinção prática:
- Se precisa do resultado da iteração →
map,filter,reduce,find,some,every - Se quer apenas o efeito da iteração →
forEach
O que muda na prática
Trocar forEach pelos métodos específicos não é refatoração por elegância. É tornar o código mais escaneável — quem lê vê a intenção antes de processar a implementação.
map diz: vou transformar cada item. filter diz: vou selecionar alguns itens. find diz: vou parar no primeiro que passar. some diz: basta um. every diz: todos devem passar.
Código que declara intenção é código que o próximo desenvolvedor — ou você daqui a seis meses — consegue ler sem precisar simular a execução mentalmente.
Esse é o argumento. O resto é consequência.
Referências


