map, filter e reduce em JavaScript com exemplos
map devolve um array do mesmo tamanho, filter devolve menos ou igual e reduce devolve qualquer coisa. Com os dois erros que cada um provoca no Node.
Os três recebem uma função e percorrem o array. A diferença está inteira no que
sai do outro lado: map devolve um array do mesmo tamanho, filter devolve
um array de tamanho menor ou igual com os itens intactos, e reduce devolve
qualquer coisa — um número, uma string, um objeto, outro array.
Guardar essa regra do tamanho resolve 90% das dúvidas de “qual eu uso aqui”.
Precisa de uma lista de preços a partir de uma lista de produtos? Mesmo número
de itens, map. Precisa só dos produtos em estoque? Menos itens, filter.
Precisa do total do carrinho? Um número, reduce.
Os três dependem de você entender funções passadas como argumento, e nenhum deles altera o array original — são o contraponto imutável dos métodos que aparecem em adicionar e remover elementos de um array.
Os três métodos fazem transformações declarativas: você descreve o que deve acontecer com cada item, e o método controla a passagem pelo array. Em palavras simples, a escolha começa pelo formato da saída que você precisa.
Três esteiras, três formatos de saída
Num centro de distribuição, uma esteira troca a etiqueta de cada caixa, outra
deixa passar apenas caixas aprovadas e uma terceira pesa todas para produzir um
total. map corresponde uma entrada a uma saída, filter seleciona entradas
inteiras e reduce acumula tudo em outro tipo de resultado. A callback muda,
mas o contrato de cada esteira permanece.
Antes do primeiro exemplo, escreva apenas o tipo e o tamanho esperados da saída.
Depois rode e confira com Array.isArray, length e o valor final. Faça essa
previsão antes de escolher o método: se a forma esperada não combina com o
contrato, a API escolhida está errada.
const carrinho = [
{ nome: 'Teclado mecânico', preco: 289.9 },
{ nome: 'Mouse sem fio', preco: 149.9 },
{ nome: 'Headset', preco: 399 },
];
const nomes = carrinho.map((item) => item.nome);
const comImposto = carrinho.map((item) => ({ ...item, preco: +(item.preco * 1.18).toFixed(2) }));
console.log(nomes);
console.log(comImposto);
console.log(carrinho.length, nomes.length, comImposto.length);A última linha é a regra em forma de saída: entraram três, saíram três, nas duas
transformações. map nunca filtra. Se você quer três de cinco, o método é
outro.
Repare nos parênteses em (item) => ({ ... }). Sem eles, a arrow entenderia a
chave como início de bloco e a função devolveria undefined — exatamente o
problema da próxima seção.
map sem return devolve um array de undefined
const precos = [289.9, 149.9, 399];
const errado = precos.map((preco) => {
preco * 1.18;
});
const certo = precos.map((preco) => {
return +(preco * 1.18).toFixed(2);
});
console.log(errado);
console.log(certo);Três undefined, um para cada item — porque map grava o retorno do callback
em cada posição, e um callback sem return retorna undefined. O tamanho
continua certo; só o conteúdo é lixo.
Não é erro em tempo de execução: nada quebra aqui. O programa segue e a tela
mostra três linhas vazias, ou três NaN depois de uma conta. Esse é o custo de
map não reclamar.
filter: mantém ou descarta, nunca transforma
O callback do filter responde uma pergunta de sim ou não. O item que passa
entra no array novo exatamente como estava:
const catalogo = [
{ nome: 'Teclado mecânico', preco: 289.9, estoque: 4 },
{ nome: 'Mouse sem fio', preco: 149.9, estoque: 0 },
{ nome: 'Headset', preco: 399, estoque: 9 },
{ nome: 'Webcam', preco: 219.9, estoque: 2 },
];
const disponiveis = catalogo.filter((p) => p.estoque > 0);
const caros = catalogo.filter((p) => p.preco > 1000);
console.log(disponiveis.map((p) => p.nome));
console.log(caros);
console.log(catalogo.length, disponiveis.length, caros.length);Quatro entraram, três passaram no primeiro filtro, zero no segundo. Quando nada
passa, filter devolve array vazio — nunca null, nunca undefined. Isso
é o que permite encadear sem checar nada: .filter(...).map(...) sobre uma
lista vazia continua funcionando.
O erro de trocar filter por map
const catalogo = [
{ nome: 'Teclado mecânico', preco: 289.9 },
{ nome: 'Mouse sem fio', preco: 149.9 },
];
const errado = catalogo.filter((p) => p.nome);
const certo = catalogo.map((p) => p.nome);
console.log(errado);
console.log(certo);
const cupons = ['PRIMEIRACOMPRA', '', null, 'FRETEGRATIS', undefined];
console.log(cupons.filter(Boolean));O primeiro filter devolveu os objetos inteiros. Ele avaliou p.nome como
verdadeiro ou falso — e toda string não vazia é verdadeira, então nada foi
descartado. Quem esperava uma lista de nomes recebeu a lista original.
A terceira saída mostra o uso legítimo dessa mesma mecânica: filter(Boolean)
descarta '', null, undefined, 0 e NaN de uma vez. É o jeito idiomático
de limpar uma lista que veio suja de um formulário ou de uma API.
reduce: dobra o array num valor só
reduce(callback, valorInicial). O callback recebe o acumulado e o item, e o
que ele devolve vira o acumulado da próxima volta:
const carrinho = [
{ nome: 'Teclado mecânico', preco: 289.9, quantidade: 1 },
{ nome: 'Mouse sem fio', preco: 149.9, quantidade: 2 },
{ nome: 'Headset', preco: 399, quantidade: 1 },
];
const total = carrinho.reduce((soma, item) => soma + item.preco * item.quantidade, 0);
const pecas = carrinho.reduce((n, item) => n + item.quantidade, 0);
const maisCaro = carrinho.reduce((a, b) => (a.preco > b.preco ? a : b));
console.log('R$', total.toFixed(2));
console.log(pecas, 'peças');
console.log(maisCaro.nome);Três reduções sobre o mesmo array, com três saídas de tipos diferentes: um número com centavos, uma contagem e um objeto. É isso que “qualquer coisa” significa.
O terceiro é o único caso comum em que não se passa valor inicial: sem ele,
o primeiro item vira o acumulado de partida e a comparação começa item contra
item. Passar 0 ali quebraria a lógica, porque 0.preco não existe.
O erro: Reduce of empty array with no initial value
const carrinho = [];
const total = carrinho.reduce((soma, item) => soma + item.preco);
console.log(total);Sem valor inicial, o reduce usa o primeiro item do array como ponto de
partida. Em array vazio não existe primeiro item, não existe nada para devolver
— e a especificação manda lançar TypeError em vez de inventar um zero.
O que torna esse erro traiçoeiro é que ele passa em todos os seus testes. O carrinho de exemplo tem itens, o array de mock tem itens; o array vazio só aparece na produção, no cliente que abriu o carrinho pela primeira vez. É um erro de caso de borda, não de lógica.
A correção é um caractere:
const carrinho = [];
const total = carrinho.reduce((soma, item) => soma + item.preco, 0);
console.log(total);
const umItem = [{ nome: 'Headset', preco: 399 }];
let chamadas = 0;
const resultado = umItem.reduce((acumulado, item) => {
chamadas += 1;
return acumulado + item.preco;
});
console.log(resultado, 'callback chamado', chamadas, 'vez(es)');A primeira linha é o total correto de um carrinho vazio: zero.
A segunda é o irmão silencioso do mesmo problema. Um array com um item e
sem valor inicial: o reduce devolveu o objeto inteiro, não o preço, e o
callback nunca foi chamado — o contador provou. Nenhum erro, nenhum aviso, e
o total do pedido virou [object Object] na tela.
Encadear filter e map
A força dos três aparece quando eles se combinam. Cada método devolve um array, então o próximo já pode ser chamado no mesmo ponto:
const catalogo = [
{ nome: 'Teclado mecânico', preco: 289.9, estoque: 4 },
{ nome: 'Mouse sem fio', preco: 149.9, estoque: 0 },
{ nome: 'Headset', preco: 399, estoque: 9 },
{ nome: 'Webcam', preco: 219.9, estoque: 2 },
];
const etiquetas = catalogo
.filter((p) => p.estoque > 0)
.map((p) => `${p.nome}: R$ ${p.preco.toFixed(2)}`);
const totalDisponivel = catalogo
.filter((p) => p.estoque > 0)
.reduce((soma, p) => soma + p.preco * p.estoque, 0);
console.log(etiquetas);
console.log('R$', totalDisponivel.toFixed(2));Ordem importa e economiza trabalho: filtre antes de mapear. Mapear os quatro para depois descartar um é uma transformação jogada fora. Com quatro produtos não muda nada; com quatro mil, muda.
O que não vale a pena é ter medo do custo. Cada método faz uma passada, e um
encadeamento de dois faz duas — mas você trocou um laço com if dentro e uma
variável de acumulação por duas linhas que dizem exatamente o que fazem.
map que devolve listas: use flatMap
Um caso em que a regra do “mesmo tamanho” atrapalha:
const pedidos = [
{ numero: 1041, itens: ['Teclado mecânico', 'Mouse sem fio'] },
{ numero: 1042, itens: ['Headset'] },
{ numero: 1043, itens: [] },
];
const comMap = pedidos.map((p) => p.itens);
const comFlatMap = pedidos.flatMap((p) => p.itens);
console.log(comMap);
console.log(comFlatMap);
console.log(comMap.length, comFlatMap.length);O map cumpriu o contrato à risca: três pedidos, três itens de saída — só que
cada um é um array, inclusive o vazio do pedido 1043. flatMap desmonta um
nível e entrega a lista corrida.
Repare que os dois length são 3 por coincidência: o comFlatMap tem três
produtos, o comMap tem três listas. Some um pedido de dois itens e a
coincidência acaba — contar a coisa errada aqui faz o resumo do pedido sair com
o número trocado.
Agrupar com reduce
O uso de reduce que mais aparece em código de verdade não é somar — é montar
um objeto de resumo:
const pedidos = [
{ numero: 1041, uf: 'SP', total: 289.9 },
{ numero: 1042, uf: 'RJ', total: 1199 },
{ numero: 1043, uf: 'SP', total: 79.5 },
{ numero: 1044, uf: 'MG', total: 399 },
];
const porUf = pedidos.reduce((mapa, pedido) => {
mapa[pedido.uf] = (mapa[pedido.uf] ?? 0) + pedido.total;
return mapa;
}, {});
console.log(porUf);
console.log(Object.entries(porUf).map(([uf, v]) => `${uf}: R$ ${v.toFixed(2)}`));Duas linhas fazem o trabalho todo. O ?? 0 cria a chave na primeira vez que a
UF aparece; o return mapa é obrigatório, porque sem ele o acumulado da próxima
volta seria undefined.
Esquecer esse return é o segundo erro mais comum do reduce, depois do valor
inicial — e ele quebra com Cannot read properties of undefined na volta
seguinte. É o mesmo diagnóstico do
TypeError: Cannot read properties of undefined,
vindo de dentro do acumulador.
Quando não usar
reduce tem fama de elegante e por isso é usado onde não deveria:
const carrinho = [
{ nome: 'Teclado mecânico', preco: 289.9 },
{ nome: 'Mouse sem fio', preco: 149.9 },
];
const totalComReduce = carrinho.reduce((soma, item) => soma + item.preco, 0);
let totalComFor = 0;
for (const item of carrinho) {
totalComFor += item.preco;
}
console.log(totalComReduce.toFixed(2), totalComFor.toFixed(2));
const relatorio = carrinho.reduce((texto, item, i) => {
return `${texto}${i > 0 ? ', ' : ''}${item.nome}`;
}, 'Pedido: ');
console.log(relatorio);As duas primeiras somas dão no mesmo. Nesse caso reduce ganha por dizer a
intenção — “isto vira um número” — logo na primeira palavra.
O segundo bloco é o contrário: aquele reduce que monta texto seria
'Pedido: ' + carrinho.map((i) => i.nome).join(', '), mais curto e mais legível.
Quando o acumulador precisa de um if para saber se é a primeira volta, é sinal
de que existe um método mais direto.
Quatro critérios para decidir:
- Sai uma lista do mesmo tamanho?
map. - Sai uma sublista, com os itens iguais?
filter. - Sai um valor só, e a operação é associativa (somar, contar, agrupar)?
reduce— com valor inicial. - Precisa parar no meio, ou o corpo tem efeito colateral (log, requisição,
escrita em tela)? Nenhum dos três: use
for...of, como mostra percorrer array em JavaScript.
Com map, filter e reduce você fecha o bloco de arrays da
trilha de JavaScript e ganha o vocabulário que
aparece em todo código React. Se algum dos exemplos tropeçou num undefined no
meio do encadeamento, arrays em JavaScript
explica de onde ele veio. O
guia completo de JavaScript mostra o que vem depois.
Prefere aprender em vídeo?
Tem aula sobre este assunto no nosso canal.
DevClub no YouTubeAPRENDA MAP, REDUCE E FILTER NA PRÁTICA | DOMINANDO JAVASCRIPTAssistir a aula
DevClub no YouTubeMAP, REDUCE E FILTER NO JAVASCRIPT | Aprenda de Uma vez por TODAS | Aprenda JavaScriptAssistir a aula
Perguntas frequentes
Como escolher entre map, filter e reduce?
Por que meu map devolveu um array de undefined?
reduce é mais rápido que um for?
Devo sempre passar o valor inicial no reduce?
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
Todo o código deste artigo foi executado em Node 24.16.0, e as saídas exibidas são as reais — como produzimos este conteúdo.
Fontes consultadas
- MDN — Array.prototype.reduce() — developer.mozilla.org
- MDN — Array.prototype.map() — developer.mozilla.org
- ECMAScript 2026 Language Specification — Array.prototype.reduce — tc39.es


