voltar aos artigosfunctional programming
$cd~/artigos/map-reduce-filter-javascript-transformacao-arrays
Functional Programming
série 33 Conceitos JS·parte 27 de 38

Map, Reduce & Filter: A Linha de Montagem dos Arrays

22 minVanderlei José Alamino

"Dê-me um array e três métodos, e eu transformarei o mundo." — adaptado de Arquimedes (provavelmente)

map(), filter() e reduce() são os cavalos de trabalho da programação funcional em JavaScript. Três estações numa linha de montagem: transformar, inspecionar e combinar — sem loops explícitos e sem mutar o array original.

snippetJavaScript
const totalSales = transactions  .filter(t => t.type === "sale")  .map(t => t.amount)  .reduce((sum, n) => sum + n, 0);

Neste guia da série 33 Conceitos JS — projeto open source criado por Leonardo Maldonado — você vai aprender:

  • O modelo mental de linha de montagem para cada método
  • Encadeamento em pipelines de dados
  • O erro crítico do reduce() sem valor inicial
  • find, some, every e callbacks assíncronos

Pré-requisitos: Higher-Order Functions e Pure Functions — esses métodos são HOFs puras por design.

map-reduce-filter-javascript-transformacao-arrays.meta.jsonjson

// metadados do artigo

{
  "tag": "functional programming",
  "readTime": "22 min",
  "author": "Vanderlei José Alamino",
  "source": "33jsconcepts.com",
  "series": "33 Conceitos JS",
  "part": {
    "current": 27,
    "total": 38
  }
}
abrir fonte original
map-reduce-filter-javascript-transformacao-arrays.mdmarkdown

Fonte de referência

Conteúdo adaptado a partir do guia original em inglês.

33jsconcepts.com
01

seção

Analogia: A Linha de Montagem

Três estações processam matérias-primas (seu array):

snippettext
INPUT → map() (pintura)     → mesmo tamanho, cada item transformado      → filter() (qualidade)  → 0 a N itens que passam no teste      → reduce() (embalagem)  → um único resultadoExemplo: [1,2,3,4] → map×2 → [2,4,6,8] → filter>4 → [6,8] → reduce sum → 14

map — entram 4, saem 4 transformados. filter — podem sair menos. reduce — muitos entram, um valor sai. A saída de uma estação alimenta a próxima.

02

seção

Visão Geral dos Três Métodos

MétodoFazRetornaOriginal
map()Transforma cada elementoNovo array (mesmo tamanho)Inalterado
filter()Mantém quem passa no testeNovo array (0 a N)Inalterado
reduce()Combina tudoQualquer tipoInalterado

Imutabilidade: nenhum altera o array original — previsível e alinhado a funções puras.

03

seção

map() — Transforme Cada Elemento

snippetJavaScript
const numbers = [1, 2, 3, 4];const doubled = numbers.map(num => num * 2); console.log(doubled);  // [2, 4, 6, 8]console.log(numbers);  // [1, 2, 3, 4] — intacto
snippetJavaScript
array.map(callback(elemento, índice, array), thisArg)

Transformações comuns

snippetJavaScript
[1, 2, 3, 4, 5].map(n => n * 2);           // [2, 4, 6, 8, 10]["hello", "world"].map(w => w.toUpperCase()); // ["HELLO", "WORLD"]

Extrair propriedades

snippetJavaScript
const users = [  { id: 1, name: "Alice", email: "alice@email.com" },  { id: 2, name: "Bob", email: "bob@email.com" },]; users.map(user => user.name);   // ["Alice", "Bob"]users.map(user => `user-${user.id}`);  // ["user-1", "user-2"]

Remodelar objetos

snippetJavaScript
users.map(user => ({  fullName: `${user.firstName} ${user.lastName}`,  isAdult: user.age >= 18,}));

Usando o índice

snippetJavaScript
["a", "b", "c"].map((letter, index) => `${index}: ${letter}`);// ["0: a", "1: b", "2: c"]
04

seção

A Armadilha do parseInt

Gotcha clássico — você consegue identificar?

snippetJavaScript
const strings = ["1", "2", "3"];strings.map(parseInt);  // [1, NaN, NaN]

map passa (elemento, índice, array). parseInt(str, radix) recebe o índice como radix:

snippetJavaScript
parseInt("1", 0);  // 1parseInt("2", 1);  // NaN — radix 1 inválidoparseInt("3", 2);  // NaN
snippetJavaScript
// ✓ Correçõesstrings.map(str => parseInt(str, 10));  // [1, 2, 3]strings.map(Number);                       // [1, 2, 3]

Veja também Higher-Order Functions.

05

seção

map() vs forEach()

Aspectomap()forEach()
RetornaNovo arrayundefined
PropósitoTransformarEfeitos colaterais
EncadeávelSimNão
snippetJavaScript
const doubled = numbers.map(n => n * 2);     // ✓ precisa do resultadonumbers.forEach(n => console.log(n));          // ✓ só executa ação // ❌ map para side effect — cria [undefined, undefined, undefined]numbers.map(n => console.log(n));
06

seção

filter() — Mantenha Correspondentes

Callback retorna true para manter, false para excluir:

snippetJavaScript
const evens = [1, 2, 3, 4, 5, 6].filter(n => n % 2 === 0);console.log(evens);  // [2, 4, 6]

Filtrar objetos

snippetJavaScript
const users = [  { name: "Alice", age: 25, active: true },  { name: "Bob", age: 17, active: true },  { name: "Charlie", age: 30, active: false },]; users.filter(u => u.active);users.filter(u => u.age >= 18);users.filter(u => u.active && u.age >= 18);

Remover falsy com Boolean

snippetJavaScript
const mixed = [0, 1, "", "hello", null, undefined, false, true, NaN, 42];mixed.filter(Boolean);  // [1, "hello", true, 42]

Falsy: false, 0, -0, 0n, "", null, undefined, NaN.

Busca em catálogo

snippetJavaScript
products.filter(p =>  p.name.toLowerCase().includes(searchTerm.toLowerCase()),);products.filter(p => p.category === "laptops");products.filter(p => p.price <= 1000);
07

seção

filter() vs find() vs some() vs every()

MétodoRetornaPara cedo?Use quando
filter()Array de matchesNãoTodos os itens
find()Primeiro ou undefinedSimUm item
some()booleanSimExiste algum?
every()booleanSimTodos passam?
snippetJavaScript
const numbers = [1, 2, 3, 4, 5]; numbers.filter(n => n % 2 === 0);  // [2, 4]numbers.find(n => n % 2 === 0);    // 2numbers.some(n => n % 2 === 0);    // truenumbers.every(n => n % 2 === 0);   // false
snippetJavaScript
// ❌ users.filter(u => u.id === 2)[0]// ✓ users.find(u => u.id === 2) // ❌ users.filter(u => u.admin).length > 0// ✓ users.some(u => u.admin)
08

seção

reduce() — Combine em Um Valor

A bola de neve: accumulator cresce a cada elemento.

snippetJavaScript
const sum = [1, 2, 3, 4, 5].reduce((acc, curr) => acc + curr, 0);console.log(sum);  // 15
snippetJavaScript
array.reduce(callback(accumulator, currentValue, índice, array), initialValue)

Passo a passo

snippetJavaScript
[1, 2, 3, 4].reduce((acc, curr) => acc + curr, 0)// 0+1=1 → 1+2=3 → 3+3=6 → 6+4=10

Soma, média, min/max

snippetJavaScript
const numbers = [10, 20, 30, 40, 50];numbers.reduce((acc, n) => acc + n, 0);                    // 150numbers.reduce((acc, n) => acc + n, 0) / numbers.length;   // 30numbers.reduce((acc, n) => (n > acc ? n : acc), numbers[0]); // maxMath.max(...numbers);  // alternativa simples

Contagem e agrupamento

snippetJavaScript
const fruits = ["apple", "banana", "apple", "orange", "banana", "apple"]; fruits.reduce((acc, fruit) => {  acc[fruit] = (acc[fruit] || 0) + 1;  return acc;}, {});// { apple: 3, banana: 2, orange: 1 }
snippetJavaScript
people.reduce((acc, person) => {  const dept = person.department;  if (!acc[dept]) acc[dept] = [];  acc[dept].push(person);  return acc;}, {}); // ES2024+: Object.groupBy(people, p => p.department)

Objeto a partir de pares e flatten

snippetJavaScript
[["name", "Alice"], ["age", 25]].reduce((acc, [k, v]) => {  acc[k] = v;  return acc;}, {}); [[1, 2], [3, 4]].reduce((acc, arr) => acc.concat(arr), []);// ou nested.flat()
09

seção

Implementando map e filter com reduce

snippetJavaScript
function myMap(array, callback) {  return array.reduce((acc, el, i) => {    acc.push(callback(el, i, array));    return acc;  }, []);} function myFilter(array, callback) {  return array.reduce((acc, el, i) => {    if (callback(el, i, array)) acc.push(el);    return acc;  }, []);}

Use reduce quando a saída não é “array transformado” ou “subset” — objetos, números agregados, lógica customizada. Para transformação/filtro simples, map/filter são mais claros.

10

seção

Encadeamento — O Verdadeiro Poder

snippetJavaScript
const transactions = [  { type: "sale", amount: 100 },  { type: "refund", amount: 30 },  { type: "sale", amount: 200 },  { type: "sale", amount: 150 },  { type: "refund", amount: 50 },]; const totalSales = transactions  .filter(t => t.type === "sale")  .map(t => t.amount)  .reduce((sum, n) => sum + n, 0); console.log(totalSales);  // 450

Leia como pipeline top-down: filter → map → filter → reduce.

E-commerce: total com desconto

snippetJavaScript
const total = cart  .map(item => {    const subtotal = item.price * item.quantity;    return subtotal - subtotal * (item.discountPercent / 100);  })  .reduce((sum, price) => sum + price, 0);

Top 3 vendedores

snippetJavaScript
salespeople  .filter(p => p.sales >= 50000)  .sort((a, b) => b.sales - a.sales)  .slice(0, 3)  .map(p => p.name);

Performance em arrays grandes

Cada método = uma passagem. Para >10k itens críticos, considere um único reduce. Abaixo disso, priorize legibilidade.

11

seção

O Erro #1: Valor Inicial do reduce()

snippetJavaScript
// ❌ Array vazio — CRASH[].reduce((acc, n) => acc + n);// TypeError: Reduce of empty array with no initial value // ✓ Sempre forneça initialValue[].reduce((acc, n) => acc + n, 0);  // 0
snippetJavaScript
const products = [  { name: "Laptop", price: 1000 },  { name: "Phone", price: 500 },]; // ❌ Sem initial — acc vira primeiro objetoproducts.reduce((acc, p) => acc + p.price);  // "[object Object]500" // ✓products.reduce((acc, p) => acc + p.price, 0);  // 1500

Regra: sempre passe initialValue — evita crash e documenta o tipo do accumulator.

12

seção

Erros Comuns

map sem return

snippetJavaScript
numbers.map(n => { n * 2; });        // [undefined, ...]numbers.map(n => { return n * 2; }); // ✓numbers.map(n => n * 2);             // ✓ implícito

map mutando objetos

snippetJavaScript
// ❌ user.score += 5 dentro do map// ✓ { ...user, score: user.score + 5 }

reduce sem retornar accumulator

snippetJavaScript
numbers.reduce((acc, n) => { acc + n; }, 0);  // undefinednumbers.reduce((acc, n) => acc + n, 0);        // ✓

reduce quando filter+map bastam

snippetJavaScript
// ❌ difícilusers.reduce((acc, u) => { if (u.active) acc.push(u.name.toUpperCase()); return acc; }, []) // ✓users.filter(u => u.active).map(u => u.name.toUpperCase());
13

seção

Outros Métodos Úteis

MétodoRetornaNota
find / findIndexelemento / índicepara cedo
some / everybooleantestes
includes / indexOfboolean / índicevalor exato
flat / flatMaparrayachatar
reduceRightanydireita → esquerda
toSorted / toReversedarrayES2023, imutável
sort / reversearraymutam!
snippetJavaScript
[3, 1, 2].toSorted();   // [1, 2, 3] — original intacto[1, 2].flatMap(n => [n, n * 2]);  // [1, 2, 2, 4]
snippettext
Transformar cada um     → map()Manter alguns           → filter()Combinar em um valor    → reduce()Primeiro match          → find()Algum / todos passam?   → some() / every()Side effect só          → forEach()
14

seção

Callbacks Assíncronos

map, filter e reduce não esperam async — rodam síncronos:

snippetJavaScript
const userIds = [1, 2, 3]; // ❌ Array de Promisesconst users = userIds.map(async id => {  const res = await fetch(`/api/users/${id}`);  return res.json();});console.log(users);  // [Promise, Promise, Promise] // ✓const users = await Promise.all(  userIds.map(async id => {    const res = await fetch(`/api/users/${id}`);    return res.json();  }),);

Async filter (dois passos)

snippetJavaScript
const checks = await Promise.all(numbers.map(n => isValid(n)));const evens = numbers.filter((_, i) => checks[i]);

Para sequencial ou limite de concorrência, prefira for...of em vez de map paralelo.

15

seção

Perguntas Clássicas de Entrevista

16

seção

Teste Seu Conhecimento

$ summary --quick

Resumo rápido

  • map — transforma cada item; mesmo tamanho; sempre retorne algo
  • filter — mantém quem passa; callback booleano
  • reduce — combina em um valor; sempre initialValue
  • Imutabilidade — original intacto; alinhado a funções puras
  • Encadeamento — filter → map → reduce = pipeline clássico
  • parseInt + map — envolva em arrow ou use Number
  • find/some/every — mais eficientes que filter quando basta um ou boolean
  • forEach — side effects; não use map só para logar
  • Async — Promise.all(map(...)); filter async em dois passos
  • Performance — legibilidade primeiro; combine reduce só se necessário

Créditos da série

Este artigo faz parte de uma série de estudos inspirada no projeto 33 Conceitos JS, criado por Leonardo Maldonado. Todo o conteúdo original está disponível em 33jsconcepts.com.