π§ Array Methods: map, filter & reduce
You already know how to loop over an array with for. But there's a cleaner, more expressive way to work with collections β one that says what you want, not the bookkeeping of how to get it. Meet the functional trio that powers modern JavaScript: map transforms, filter selects, and reduce accumulates.
Week 2 · Day 1 (Monday: Arrays and Objects) · Lecture 3
π― Learning Objectives
By the end of this lesson, you will be able to:
- Use
map()to transform every element into a new array of the same length - Use
filter()to keep only the elements that pass a test - Use
reduce()to boil an array down to a single value (a sum, an object, anything) - Chain
filter β map β reduceinto readable data pipelines - Recognize that all three return new values and never mutate the original array
- Pick the right tool β including
find,some, andeveryβ for the job
Estimated Time: 70 minutes
Practice: Convert temperatures, strip falsy values, rebuild map with reduce, and analyze a student dataset.
In This Lesson
Why Functional Methods?
Imagine a chef standing at a conveyor belt of ingredients. map transforms each ingredient β chopping every vegetable. filter keeps only some β picking out the ripe tomatoes. reduce combines everything into one result β simmering it all into a single pot of soup. Each takes a small function and applies it across the whole array for you.
same length] A --> D{filter} D --> E[New Array
fewer or equal] A --> F{reduce} F --> G[Single Value]
Compared to a hand-written for loop, these methods are declarative: they describe the goal, hide the loop counter and index arithmetic, and β crucially β return a new value without ever mutating the original. That immutability makes your code easier to reason about and is the norm in modern JavaScript and React.
[1, 2, 3] through each method: map keeps the count, filter can shrink it, reduce collapses it to one value.map() β Transform Every Element
The map() method creates a new array by running a function on every element of the original. The result is always the same length as the input β one output per input. Think of it as a magic wand that reshapes each item.
Syntax & simple examples
// array.map(callback(currentValue, index, array)) β new array
// Double every number
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
console.log(numbers); // [1, 2, 3, 4, 5] β original untouched
// Uppercase every string
const fruits = ['apple', 'banana', 'orange'];
console.log(fruits.map(fruit => fruit.toUpperCase())); // ['APPLE', 'BANANA', 'ORANGE']
// Pull one property out of each object
const users = [
{ id: 1, name: 'John', age: 30 },
{ id: 2, name: 'Jane', age: 25 },
{ id: 3, name: 'Bob', age: 35 }
];
console.log(users.map(user => user.name)); // ['John', 'Jane', 'Bob']
Real-world: reshape API data for the UI
// Server sends snake_case; the UI wants a tidy shape
const apiResponse = [
{ user_id: 1, first_name: 'John', last_name: 'Doe', email_address: 'john@example.com' },
{ user_id: 2, first_name: 'Jane', last_name: 'Smith', email_address: 'jane@example.com' }
];
const uiData = apiResponse.map(user => ({
id: user.user_id,
fullName: `${user.first_name} ${user.last_name}`,
email: user.email_address,
initials: `${user.first_name[0]}${user.last_name[0]}`
}));
console.log(uiData[0]);
// { id: 1, fullName: 'John Doe', email: 'john@example.com', initials: 'JD' }
β οΈ Wrap object literals in parentheses
To return an object from an arrow function you must wrap it: user => ({ id: user.id }). Without the parentheses, { } is read as a function body, not an object β a classic map mistake.
filter() β Keep Only What Passes
The filter() method builds a new array containing only the elements for which your test function returns true. It's the bouncer at the door: elements that fail the check don't get in. The result is the same length or shorter.
Syntax & simple examples
// array.filter(callback(element, index, array)) β new array
// Return true to KEEP the element, false to drop it
// Keep only even numbers
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
console.log(numbers.filter(num => num % 2 === 0)); // [2, 4, 6, 8, 10]
// Keep words longer than 5 characters
const words = ['cat', 'elephant', 'dog', 'rhinoceros', 'bird'];
console.log(words.filter(word => word.length > 5)); // ['elephant', 'rhinoceros']
// Keep objects that pass a property test
const products = [
{ name: 'Laptop', price: 999, inStock: true },
{ name: 'Phone', price: 599, inStock: false },
{ name: 'Tablet', price: 349, inStock: true }
];
console.log(products.filter(p => p.inStock)); // [Laptop, Tablet]
Real-world: multi-criteria search
const inventory = [
{ id: 1, name: 'Laptop', price: 999, category: 'Electronics', rating: 4.5 },
{ id: 2, name: 'Desk', price: 199, category: 'Furniture', rating: 4.0 },
{ id: 3, name: 'Phone', price: 699, category: 'Electronics', rating: 4.8 },
{ id: 4, name: 'Chair', price: 149, category: 'Furniture', rating: 3.9 }
];
function filterProducts(minPrice, maxPrice, category, minRating) {
return inventory.filter(product =>
product.price >= minPrice &&
product.price <= maxPrice &&
product.category === category &&
product.rating >= minRating
);
}
console.log(filterProducts(100, 500, 'Furniture', 3.5));
// [{ id: 2, name: 'Desk', ... }, { id: 4, name: 'Chair', ... }]
β
A neat trick: filter(Boolean)
Passing the Boolean function to filter drops every "falsy" value (0, '', null, undefined, NaN, false) in one stroke: [0, 1, '', 2, null, 3].filter(Boolean) gives [1, 2, 3].
reduce() β Accumulate to a Single Value
The reduce() method is the most powerful β and the most feared β of the three. It walks the array carrying an accumulator from one element to the next, and returns whatever the accumulator ends up as. That final value can be a number, a string, an object, or even another array. Picture a snowball rolling downhill, gathering snow as it goes.
Syntax & simple examples
// array.reduce(callback(accumulator, currentValue, index, array), initialValue)
// Sum all numbers (initial accumulator = 0)
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((acc, num) => acc + num, 0);
console.log(sum); // 15
// Find the maximum (no initialValue β starts from the first element)
const values = [10, 5, 25, 15, 30];
const max = values.reduce((acc, val) => (val > acc ? val : acc));
console.log(max); // 30
// Count occurrences β the accumulator is an OBJECT
const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];
const fruitCount = fruits.reduce((acc, fruit) => {
acc[fruit] = (acc[fruit] || 0) + 1;
return acc;
}, {});
console.log(fruitCount); // { apple: 3, banana: 2, orange: 1 }
β οΈ Almost always pass an initialValue
The second argument to reduce is the accumulator's starting value. Omit it and reduce uses the first element as the seed β which breaks on an empty array (it throws). Passing 0, {}, or [] explicitly makes your intent clear and your code safe.
Advanced: group objects by a property
const people = [
{ name: 'Alice', age: 25, city: 'NYC' },
{ name: 'Bob', age: 30, city: 'LA' },
{ name: 'Carol', age: 25, city: 'NYC' },
{ name: 'Dave', age: 30, city: 'LA' }
];
const groupByAge = people.reduce((acc, person) => {
const key = person.age;
(acc[key] ||= []).push(person); // create the bucket if needed, then add
return acc;
}, {});
console.log(Object.keys(groupByAge)); // ['25', '30']
π‘ Modern shortcut: Object.groupBy
Grouping is so common that JavaScript added Object.groupBy(people, p => p.age) (ES2024). Where it's available it replaces the manual reduce above β but understanding the reduce version teaches you the underlying pattern.
Chaining Methods
Because map and filter each return a new array, you can chain them into an assembly line where each stage does one job. Read a chain top-to-bottom like a recipe.
const products = [
{ name: 'Laptop', price: 999, category: 'Electronics', sold: 150 },
{ name: 'Phone', price: 599, category: 'Electronics', sold: 300 },
{ name: 'Desk', price: 199, category: 'Furniture', sold: 80 },
{ name: 'Chair', price: 149, category: 'Furniture', sold: 120 },
{ name: 'Monitor', price: 299, category: 'Electronics', sold: 90 }
];
// Top 3 electronics by revenue, formatted for display
const topElectronics = products
.filter(p => p.category === 'Electronics') // 1. keep electronics
.map(p => ({ ...p, revenue: p.price * p.sold })) // 2. compute revenue
.sort((a, b) => b.revenue - a.revenue) // 3. highest first
.slice(0, 3) // 4. take top 3
.map(p => `${p.name}: $${p.revenue.toLocaleString()}`); // 5. format
console.log(topElectronics);
// ['Phone: $179,700', 'Laptop: $149,850', 'Monitor: $26,910']
Combining reduce for a summary
const users = [
{ id: 1, name: 'John', orders: [{ amount: 50 }, { amount: 75 }] },
{ id: 2, name: 'Jane', orders: [{ amount: 100 }, { amount: 50 }, { amount: 25 }] },
{ id: 3, name: 'Bob', orders: [{ amount: 200 }] }
];
const bigSpenders = users
.map(user => ({
name: user.name,
totalOrders: user.orders.length,
totalSpent: user.orders.reduce((sum, o) => sum + o.amount, 0)
}))
.filter(user => user.totalSpent > 100)
.sort((a, b) => b.totalSpent - a.totalSpent)
.map(user => ({
name: user.name,
averageOrderValue: (user.totalSpent / user.totalOrders).toFixed(2)
}));
console.log(bigSpenders);
// [ { name: 'Jane', averageOrderValue: '58.33' }, { name: 'Bob', averageOrderValue: '200.00' } ]
Choosing the Right Method
Reaching for reduce when map or filter would do makes code harder to read. Match the method to the shape of the result you want.
| You want⦠| Use | Returns |
|---|---|---|
| A new array, one item per input | map() | Array (same length) |
| A subset that passes a test | filter() | Array (β€ length) |
| One combined value | reduce() | Any single value |
| The first matching element | find() | Element or undefined |
| "Does at least one match?" | some() | Boolean |
| "Do all of them match?" | every() | Boolean |
const products = [
{ name: 'Laptop', price: 999, inStock: true },
{ name: 'Phone', price: 599, inStock: true }
];
// find() β stop at the first match (don't filter then take [0])
const firstCheap = products.find(p => p.price < 700); // { name: 'Phone', ... }
// some() / every() β quick boolean checks
console.log(products.some(p => p.price > 900)); // true β at least one
console.log(products.every(p => p.inStock)); // true β all of them
π‘ A note on performance
Each chained method loops the array once, so filter().map() is two passes. For everyday data sizes that's completely fine β favor readability. Only reach for a single combined reduce (or a plain loop) when profiling shows a genuinely hot path over very large arrays.
Practice & Quiz
ποΈ Exercise 1: Celsius β Fahrenheit with map
Goal: Write celsiusToFahrenheit(temps) that returns a new array of Fahrenheit values using map.
function celsiusToFahrenheit(temps) {
// TODO: map each celsius value to (c * 9/5) + 32
}
console.log(celsiusToFahrenheit([0, 10, 20, 30, 40]));
// should log: [32, 50, 68, 86, 104]
π‘ Hint
The transform is a one-liner: each element c becomes (c * 9 / 5) + 32.
β Solution
function celsiusToFahrenheit(temps) {
return temps.map(c => (c * 9 / 5) + 32);
}
console.log(celsiusToFahrenheit([0, 10, 20, 30, 40])); // [32, 50, 68, 86, 104]
ποΈ Exercise 2: Strip falsy values with filter
Goal: Write removeFalsy(arr) that returns a new array with every falsy value removed.
π‘ Hint
filter keeps elements whose callback returns true. The Boolean function converts any value to exactly that.
β Solution
function removeFalsy(arr) {
return arr.filter(Boolean);
}
const mixed = [0, 1, false, 2, '', 3, null, undefined, 4, NaN, 5];
console.log(removeFalsy(mixed)); // [1, 2, 3, 4, 5]
ποΈ Exercise 3: Rebuild map using reduce
Goal: Implement mapWithReduce(arr, fn) that behaves like arr.map(fn) but is built on reduce β a great way to prove you understand the accumulator.
β Solution
function mapWithReduce(arr, callback) {
return arr.reduce((acc, item, index) => {
acc.push(callback(item, index, arr)); // start with [], push each result
return acc;
}, []);
}
console.log(mapWithReduce([1, 2, 3, 4, 5], x => x * 2)); // [2, 4, 6, 8, 10]
ποΈ Exercise 4: Average grade of passing students
Goal: Chain filter, map, and reduce to compute the average grade of the active students who are passing (average β₯ 70).
β Solution
const students = [
{ name: 'Alice', grades: [85, 92, 88], status: 'active' },
{ name: 'Bob', grades: [75, 68, 72], status: 'active' },
{ name: 'Carol', grades: [92, 94, 90], status: 'active' },
{ name: 'Dave', grades: [65, 70, 68], status: 'inactive' },
{ name: 'Eve', grades: [95, 98, 92], status: 'active' }
];
function passingAverage(students, passingGrade = 70) {
const averages = students
.filter(s => s.status === 'active')
.map(s => s.grades.reduce((sum, g) => sum + g, 0) / s.grades.length)
.filter(avg => avg >= passingGrade);
if (averages.length === 0) return 0;
return averages.reduce((sum, avg) => sum + avg, 0) / averages.length;
}
console.log(passingAverage(students).toFixed(2)); // "90.11"
π― Quick Quiz
Question 1: Which method always returns a new array the same length as the original?
Question 2: What is the purpose of reduce's second argument?
Question 3: You need just the first product under $700. Which is the best fit?
Best Practices & Pitfalls
β Do
- Match the method to the result shape:
mapfor a new array,filterfor a subset,reducefor one value - Always pass
reducean explicitinitialValue - Keep callbacks small and pure β no side effects, no mutating outer state
- Use
find,some, andeveryfor single-item and boolean questions
β Don't
- Use
mapwhen you're ignoring the returned array β that's whatforEach(or a loop) is for - Forget the parentheses when returning an object literal from an arrow:
x => ({ ... }) - Mutate the original array or its objects inside a callback
- Cram every operation into one clever
reducewhen a readable chain is clearer
β οΈ map vs forEach
// β map used only for its side effect β wasteful, returns an unused array
items.map(item => console.log(item));
// β
use forEach when you just want to DO something per element
items.forEach(item => console.log(item));
// β
use map when you want a NEW array back
const labels = items.map(item => item.name);
Summary
π Key Takeaways
map()transforms each element β a new array of the same lengthfilter()keeps elements that pass a test β a shorter-or-equal arrayreduce()accumulates β a single value of any type (give it an initial value)- All three are non-mutating and chainable into readable pipelines
- Reach for
find,some,everyfor single-item and yes/no questions
π Additional Resources
π What's Next?
You've mastered storing and transforming data in memory. Now it's time to put that data on the screen: the next lesson begins the DOM, starting with Selecting DOM Elements β how JavaScript reaches into the HTML page.
π Excellent!
map, filter, and reduce are the backbone of data work in JavaScript. Every framework you'll learn leans on them β you're now fluent in the essentials.