Map, Filter, and Reduce
Map, Filter, and Reduce
The map, filter, and reduce methods are the cornerstone of functional array processing in JavaScript. They allow you to transform, select, and aggregate data without writing manual loops or mutating the original array.
map
map creates a new array by applying a callback to every element. The callback receives the current element, its index, and the array itself:
const prices = [10.99, 24.99, 5.49];
const withTax = prices.map(price => +(price * 1.08).toFixed(2));
console.log(withTax); // [11.87, 26.99, 5.93]
A common pattern is mapping an array of objects to extract a single property:
const users = [
{ name: 'Alice', age: 30 },
{ name: 'Bob', age: 25 },
{ name: 'Carol', age: 35 }
];
const names = users.map(u => u.name);
console.log(names); // ['Alice', 'Bob', 'Carol']
filter
filter returns a new array containing only elements for which the callback returns true. The callback signature is identical to map:
const numbers = [1, 2, 3, 4, 5, 6];
const evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4, 6]
You can combine filter and map for chained transformations:
const products = [
{ name: 'Laptop', price: 999 },
{ name: 'Phone', price: 699 },
{ name: 'Tablet', price: 399 },
{ name: 'Watch', price: 249 }
];
const expensiveNames = products
.filter(p => p.price > 400)
.map(p => p.name);
console.log(expensiveNames); // ['Laptop', 'Phone']
reduce
reduce processes all elements into a single value. It takes a callback with an accumulator and current value, plus an optional initial value:
const cart = [
{ item: 'Shirt', qty: 2, price: 25 },
{ item: 'Jeans', qty: 1, price: 60 },
{ item: 'Socks', qty: 3, price: 5 }
];
const total = cart.reduce((sum, item) => sum + item.qty * item.price, 0);
console.log(total); // 125
reduce can also build objects or arrays. For example, grouping items by category:
const items = [
{ name: 'Apple', type: 'fruit' },
{ name: 'Carrot', type: 'vegetable' },
{ name: 'Banana', type: 'fruit' },
{ name: 'Broccoli', type: 'vegetable' }
];
const grouped = items.reduce((acc, item) => {
acc[item.type] = acc[item.type] || [];
acc[item.type].push(item.name);
return acc;
}, {});
console.log(grouped);
// { fruit: ['Apple', 'Banana'], vegetable: ['Carrot', 'Broccoli'] }
All three methods never mutate the original array, making your code predictable and easier to debug.
Find, Some, and Every
Find, Some, and Every
These methods let you search and test array elements using predicate callbacks. They are more readable and expressive than for loops with break or flag variables.
find
find returns the first element that satisfies the test, or undefined if none match:
const employees = [
{ id: 1, name: 'Alice', role: 'Engineer' },
{ id: 2, name: 'Bob', role: 'Designer' },
{ id: 3, name: 'Carol', role: 'Engineer' }
];
const engineer = employees.find(e => e.role === 'Engineer');
console.log(engineer); // { id: 1, name: 'Alice', role: 'Engineer' }
If you need the index of the match, use findIndex:
const idx = employees.findIndex(e => e.name === 'Bob');
console.log(idx); // 1
some
some returns true if at least one element passes the test. It short-circuits on the first match:
const ages = [22, 17, 35, 14, 28];
const hasAdult = ages.some(age => age >= 18);
console.log(hasAdult); // true
A practical use case is checking for permission flags:
const userRoles = ['viewer', 'editor'];
const canPublish = userRoles.some(role => role === 'admin' || role === 'editor');
console.log(canPublish); // true
every
every returns true only if all elements pass the test. It also short-circuits on the first failure:
const scores = [85, 92, 78, 95];
const allPassing = scores.every(score => score >= 60);
console.log(allPassing); // true
You can validate form fields with every:
const fields = [
{ name: 'email', value: 'alice@example.com' },
{ name: 'password', value: 's3cure!' },
{ name: 'age', value: '28' }
];
const allFilled = fields.every(f => f.value.trim().length > 0);
console.log(allFilled); // true
Comparison Table
| Method | Returns | Short-circuits | Use case |
|---|---|---|---|
find |
First match | Yes | Retrieve a specific element |
findIndex |
Index of match | Yes | Locate position in array |
some |
Boolean | Yes | Check if any element qualifies |
every |
Boolean | Yes | Validate all elements qualify |
These methods work exclusively with arrays, but you can convert array-like objects (NodeList, arguments) using Array.from or the spread operator before calling them.
Sort, Flat, and Array.from
Sort, Flat, and Array.from
These utility methods handle reordering, flattening, and creating arrays from non-array iterables.
sort
sort sorts the array in place and returns the same reference. By default, it sorts lexicographically (as strings), so always provide a comparator for numbers:
const nums = [40, 1, 5, 200];
nums.sort();
console.log(nums); // [1, 200, 40, 5] — not what you want!
nums.sort((a, b) => a - b);
console.log(nums); // [1, 5, 40, 200]
To sort without mutating the original, spread into a new array first:
const original = [3, 1, 4, 1, 5];
const sorted = [...original].sort((a, b) => a - b);
console.log(sorted); // [1, 1, 3, 4, 5]
console.log(original); // [3, 1, 4, 1, 5] — unchanged
Sorting objects by a property:
const users = [
{ name: 'Alice', score: 88 },
{ name: 'Bob', score: 95 },
{ name: 'Carol', score: 72 }
];
const ranked = [...users].sort((a, b) => b.score - a.score);
console.log(ranked.map(u => u.name)); // ['Bob', 'Alice', 'Carol']
flat
flat flattens nested arrays by one level by default. Pass Infinity to flatten all levels:
const nested = [1, [2, 3], [4, [5, 6]]];
console.log(nested.flat()); // [1, 2, 3, 4, [5, 6]]
console.log(nested.flat(2)); // [1, 2, 3, 4, 5, 6]
console.log(nested.flat(Infinity)); // [1, 2, 3, 4, 5, 6]
A common use case is flattening results from nested API calls:
const departments = [
['Alice', 'Bob'],
['Carol'],
['Dave', 'Eve', 'Frank']
];
const allEmployees = departments.flat();
console.log(allEmployees); // ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank']
flatMap combines map followed by flat(1) for performance:
const sentences = ['Hello world', 'Goodbye moon'];
const words = sentences.flatMap(s => s.split(' '));
console.log(words); // ['Hello', 'world', 'Goodbye', 'moon']
Array.from
Array.from creates a new array from array-like objects (NodeList, arguments, Sets, Maps) or iterables. It accepts an optional mapping function as the second argument:
// Convert a NodeList to an array
const buttons = document.querySelectorAll('button');
const btnArray = Array.from(buttons);
btnArray.forEach(btn => btn.classList.add('active'));
Create an array of sequential numbers:
const range = Array.from({ length: 5 }, (_, i) => i + 1);
console.log(range); // [1, 2, 3, 4, 5]
Clone and transform in one step:
const original = [1, 2, 3];
const doubled = Array.from(original, n => n * 2);
console.log(doubled); // [2, 4, 6]
Convert a string to an array of characters:
const chars = Array.from('hello');
console.log(chars); // ['h', 'e', 'l', 'l', 'o']
Unlike Array.prototype.slice, Array.from works with any iterable, making it essential for working with ES6 data structures like Set and Map.
Destructuring and Spread
Destructuring and Spread
Destructuring assignment and the spread operator provide concise syntax for extracting and combining array values.
Basic Destructuring
Extract values from the left side of the assignment:
const rgb = [255, 128, 0];
const [red, green, blue] = rgb;
console.log(red, green, blue); // 255 128 0
Skipping Elements
Use commas to skip positions you do not need:
const coords = [10, 20, 30, 40];
const [x, , z] = coords;
console.log(x, z); // 10 30
Rest Pattern
Collect remaining elements into a new array using the rest operator (...):
const [first, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(rest); // [2, 3, 4, 5]
Default Values
Provide fallback values when array elements might be undefined:
const [a = 10, b = 20, c = 30] = [1, 2];
console.log(a, b, c); // 1 2 30
Swapping Variables
Swap two variables without a temporary variable:
let x = 1;
let y = 2;
[x, y] = [y, x];
console.log(x, y); // 2 1
Spread Operator
Spread expands an iterable into individual elements. Common uses:
// Merging arrays
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const merged = [...arr1, ...arr2];
console.log(merged); // [1, 2, 3, 4, 5, 6]
// Adding elements without mutation
const base = ['a', 'b'];
const extended = [...base, 'c', 'd'];
console.log(extended); // ['a', 'b', 'c', 'd']
console.log(base); // ['a', 'b'] — unchanged
Destructuring in Function Parameters
Accept arrays directly as function arguments:
function greet([first, last]) {
return `Hello, ${first} ${last}!`;
}
console.log(greet(['Alice', 'Smith'])); // 'Hello, Alice Smith!'
Practical Example — Returning Multiple Values
Destructuring shines when a function needs to return multiple values:
function getStats(numbers) {
const sorted = [...numbers].sort((a, b) => a - b);
const sum = sorted.reduce((acc, n) => acc + n, 0);
return [
sorted[0], // min
sorted[sorted.length - 1], // max
sum / sorted.length // average
];
}
const [min, max, avg] = getStats([3, 1, 4, 1, 5, 9, 2, 6]);
console.log({ min, max, avg }); // { min: 1, max: 9, avg: 3.875 }
Destructuring and spread are syntax features, not methods, so they work with any array or iterable — no runtime overhead.
Quiz
1. What does the `map` method return?
2. What is the key difference between `some` and `every`?
3. Given `const arr = [3, 1, 4, 1, 5]; const sorted = [...arr].sort((a, b) => a - b);`, what are the values of `arr` and `sorted`?
Flashcards
Question
What is the difference between `map` and `forEach`?
Click to reveal answer
Answer
map returns a new array of transformed values and is designed for functional pipelines. forEach executes a side effect on each element and always returns undefined. Use map when you need the output; use forEach for logging, DOM updates, or other side effects.
Question
When should you use `reduce` instead of `map` and `filter`?
Click to reveal answer
Answer
Use reduce when you need to accumulate a single output from the entire array — such as computing a sum, building a lookup object, or grouping data. Map produces a 1-to-1 array, and filter produces a subset. Reduce handles any aggregation that requires maintaining state between iterations.
Question
Does `sort` mutate the original array? How do you avoid mutation?
Click to reveal answer
Answer
Yes, sort mutates the array in place and returns the same reference. To avoid mutation, spread the array into a new copy before sorting: const sorted = [...arr].sort((a, b) => a - b).
Revision Notes
Key Takeaways
- 1. map, filter, and reduce never mutate the original array — they return new arrays or values, making functional pipelines safe and composable
- 2. Always provide a comparator function to sort for numeric data; the default sort is lexicographic and produces unexpected results for numbers
- 3. find returns the first matching element; some and every short-circuit, stopping as soon as the result is determined, which improves performance
- 4. flat(n) flattens n levels deep; flat(Infinity) flattens all nesting levels; flatMap combines map + flat(1) for efficiency
- 5. Array.from converts array-likes (NodeList, arguments, Sets) into true arrays with built-in mapping, while spread (...) works for any iterable
- 6. Destructuring extracts values by position; use rest (...) to collect remaining elements; default values handle undefined gracefully
Interview Tips
- • Be ready to implement map, filter, and reduce from scratch — interviewers often ask for polyfills to test your understanding of callbacks and accumulator patterns
- • Know when to use which method: map for transformation, filter for selection, reduce for aggregation, find for lookup, some/every for validation
- • Explain that sort mutates in place and why spreading first is the best practice for non-destructive sorting
- • Discuss short-circuiting behavior of some and every — it is a performance optimization, not just a language quirk
- • Be able to trace through a chained pipeline like arr.filter(...).map(...).reduce(...) and predict the output step by step
- • Mention that Array.from works with any iterable (strings, Sets, Maps, generators) whereas Array.from vs spread is a common trade-off for cloning
Cheat Sheet
map(cb) → new array of same length with transformed values | filter(cb) → new array with elements where cb returns true | reduce(cb, init) → single accumulated value | find(cb) → first match or undefined | findIndex(cb) → index of first match or -1 | some(cb) → true if any element passes | every(cb) → true if all pass | sort(cb) → mutates, sorts in place | flat(n) → flattens n levels | flatMap(cb) → map then flat(1) | Array.from(iterable, cb) → array from any iterable | [...arr] → shallow copy via spread | const [a, ...rest] = arr → destructuring with rest pattern