⚡ Lesson 11: Array Methods
Stop writing manual loops. JavaScript's built-in array methods let you transform, filter, and reduce data in clean, expressive one-liners.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Transform every element with
map() - Keep only matching elements with
filter() - Collapse an array into a single value with
reduce() - Test conditions with
every()andsome() - Sort arrays with
sort()and custom comparators - Chain methods together for multi-step transformations
- Choose the right method for common data tasks
Estimated Time: 55 minutes
Project: Build a product catalog data pipeline
📑 In This Lesson
Introduction
In previous lessons, you used for loops and for...of to process arrays. Those work fine, but JavaScript provides a set of higher-order methods — functions that take another function as an argument — that make common tasks much cleaner.
Instead of writing a loop, creating an empty array, and pushing results, you call a single method that does it all. The result is code that's shorter, more readable, and less prone to bugs.
(same length)"] C --> F["New Array
(fewer items)"] D --> G["Single Value"] style A fill:#fef3c7,stroke:#f59e0b,stroke-width:2px,color:#1e293b style E fill:#ecfdf5,stroke:#22c55e,stroke-width:2px,color:#1e293b style F fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style G fill:#fce7f3,stroke:#ec4899,stroke-width:2px,color:#1e293b
All of these methods share the same pattern: you pass in a callback function that describes what to do with each element. The method handles the looping for you.
forEach — Do Something with Each
You already saw forEach briefly in Lesson 9. It runs a function for each element but doesn't return anything. Use it for side effects like logging, updating the DOM, or sending data.
const names = ["Alice", "Bob", "Carol"];
names.forEach((name, index) => {
console.log(`${index + 1}. ${name}`);
});
// 1. Alice
// 2. Bob
// 3. Carol
⚠️ forEach Returns undefined
If you need a new array, use map or filter instead. A common mistake is trying to use forEach to build a new array — it's the wrong tool for that.
// ❌ Wrong — forEach returns undefined
const doubled = names.forEach(n => n.toUpperCase());
console.log(doubled); // undefined!
// ✅ Right — use map to create a new array
const uppercased = names.map(n => n.toUpperCase());
console.log(uppercased); // ["ALICE", "BOB", "CAROL"]
map — Transform Every Element
map() creates a new array by applying a function to every element. The new array always has the same length as the original.
const prices = [10, 20, 30, 40];
// Add 8% tax to each price
const withTax = prices.map(price => price * 1.08);
console.log(withTax); // [10.8, 21.6, 32.4, 43.2]
// Original is unchanged
console.log(prices); // [10, 20, 30, 40]
Compare: Loop vs. map
const names = ["alice", "bob", "carol"];
// ❌ Manual loop — more code, more room for error
const upper1 = [];
for (const name of names) {
upper1.push(name.toUpperCase());
}
// ✅ map — one clean line
const upper2 = names.map(name => name.toUpperCase());
// Both produce: ["ALICE", "BOB", "CAROL"]
map with Objects
const users = [
{ name: "Alice", age: 28 },
{ name: "Bob", age: 34 },
{ name: "Carol", age: 22 }
];
// Extract just the names
const names = users.map(user => user.name);
console.log(names); // ["Alice", "Bob", "Carol"]
// Create display strings
const labels = users.map(user => `${user.name} (${user.age})`);
console.log(labels); // ["Alice (28)", "Bob (34)", "Carol (22)"]
// Transform each object
const withBirthYear = users.map(user => ({
...user,
birthYear: 2026 - user.age
}));
💡 map Always Returns the Same Number of Elements
If you start with 5 elements, you'll get 5 back. If you need fewer elements, use filter. If you need a single value, use reduce.
filter — Keep What Matches
filter() creates a new array containing only the elements where the callback returns true. Elements that return false are excluded.
const scores = [85, 42, 93, 67, 78, 55, 91];
// Keep only passing scores (70+)
const passing = scores.filter(score => score >= 70);
console.log(passing); // [85, 93, 67, 78, 91]
// Wait — 67 isn't >= 70! Let me fix that:
const actuallyPassing = scores.filter(score => score >= 70);
console.log(actuallyPassing); // [85, 93, 78, 91]
// Original unchanged
console.log(scores); // [85, 42, 93, 67, 78, 55, 91]
Filtering Objects
const products = [
{ name: "Laptop", price: 999, inStock: true },
{ name: "Phone", price: 699, inStock: false },
{ name: "Tablet", price: 449, inStock: true },
{ name: "Watch", price: 299, inStock: true },
{ name: "Monitor", price: 549, inStock: false }
];
// Only in-stock products
const available = products.filter(p => p.inStock);
console.log(available.length); // 3
// Products under $500
const affordable = products.filter(p => p.price < 500);
console.log(affordable);
// [{ name: "Tablet", ... }, { name: "Watch", ... }]
// Combine conditions
const deals = products.filter(p => p.inStock && p.price < 500);
console.log(deals.map(p => p.name)); // ["Tablet", "Watch"]
Removing Items with filter
const todos = ["Buy milk", "Walk dog", "Code project", "Walk dog"];
// Remove all instances of "Walk dog" (non-mutating!)
const updated = todos.filter(todo => todo !== "Walk dog");
console.log(updated); // ["Buy milk", "Code project"]
console.log(todos); // Original unchanged
reduce — Collapse to One Value
reduce() processes every element and accumulates them into a single value — a number, string, object, or even another array. It's the most powerful (and most confusing) array method.
// Syntax: array.reduce((accumulator, currentValue) => ..., initialValue)
const numbers = [10, 20, 30, 40];
// Sum all numbers
const total = numbers.reduce((sum, num) => sum + num, 0);
console.log(total); // 100
How reduce Works Step by Step
const numbers = [10, 20, 30, 40];
const total = numbers.reduce((sum, num) => {
console.log(`sum: ${sum}, num: ${num}, result: ${sum + num}`);
return sum + num;
}, 0);
// sum: 0, num: 10, result: 10
// sum: 10, num: 20, result: 30
// sum: 30, num: 30, result: 60
// sum: 60, num: 40, result: 100
Common reduce Patterns
const cart = [
{ item: "Shirt", price: 25 },
{ item: "Pants", price: 45 },
{ item: "Shoes", price: 80 }
];
// Sum prices
const cartTotal = cart.reduce((total, product) => total + product.price, 0);
console.log(cartTotal); // 150
// Find the maximum value
const scores = [72, 95, 88, 63, 91];
const highest = scores.reduce((max, score) => score > max ? score : max, scores[0]);
console.log(highest); // 95
// Count occurrences
const fruits = ["apple", "banana", "apple", "cherry", "banana", "apple"];
const counts = fruits.reduce((tally, fruit) => {
tally[fruit] = (tally[fruit] || 0) + 1;
return tally;
}, {});
console.log(counts); // { apple: 3, banana: 2, cherry: 1 }
// Group by category
const items = [
{ name: "Apple", type: "fruit" },
{ name: "Carrot", type: "vegetable" },
{ name: "Banana", type: "fruit" },
{ name: "Broccoli", type: "vegetable" }
];
const grouped = items.reduce((groups, item) => {
const key = item.type;
if (!groups[key]) groups[key] = [];
groups[key].push(item.name);
return groups;
}, {});
console.log(grouped);
// { fruit: ["Apple", "Banana"], vegetable: ["Carrot", "Broccoli"] }
⚠️ Always Provide an Initial Value
If you omit the initial value, reduce uses the first element as the starting accumulator and begins iteration from the second element. This works for simple sums but fails with empty arrays and causes subtle bugs. Always pass an explicit initial value.
// ❌ No initial value — crashes on empty arrays
// [].reduce((sum, n) => sum + n); // TypeError!
// ✅ Always provide an initial value
[].reduce((sum, n) => sum + n, 0); // 0 — no crash
find, some & every
These methods answer questions about your array rather than transforming it.
find() — First Match (Review)
You saw find in Lesson 9. It returns the first element matching the condition, or undefined.
const users = [
{ name: "Alice", role: "admin" },
{ name: "Bob", role: "editor" },
{ name: "Carol", role: "admin" }
];
const firstAdmin = users.find(u => u.role === "admin");
console.log(firstAdmin); // { name: "Alice", role: "admin" }
some() — Does At Least One Match?
some() returns true if at least one element passes the test. It short-circuits — stops as soon as it finds a match.
const ages = [15, 22, 17, 19, 14];
const hasAdult = ages.some(age => age >= 18);
console.log(hasAdult); // true
const allTeens = ages.some(age => age >= 30);
console.log(allTeens); // false
// Practical: check if cart has any out-of-stock items
const cart = [
{ name: "Laptop", inStock: true },
{ name: "Phone", inStock: false },
{ name: "Tablet", inStock: true }
];
const hasUnavailable = cart.some(item => !item.inStock);
console.log(hasUnavailable); // true
every() — Do All Match?
every() returns true only if all elements pass the test. It short-circuits on the first failure.
const scores = [85, 92, 78, 95, 88];
const allPassing = scores.every(score => score >= 70);
console.log(allPassing); // true
const allExcellent = scores.every(score => score >= 90);
console.log(allExcellent); // false (78 and 88 fail)
// Practical: check if all form fields are filled
const formFields = ["Ray", "ray@example.com", "Hello there!"];
const allFilled = formFields.every(field => field.trim().length > 0);
console.log(allFilled); // true
| Method | Returns | Question It Answers |
|---|---|---|
find(fn) | Element or undefined | What's the first match? |
findIndex(fn) | Index or -1 | Where's the first match? |
some(fn) | true / false | Does any element match? |
every(fn) | true / false | Do all elements match? |
sort — Ordering Arrays
sort() arranges elements in place. By default it sorts alphabetically as strings — which produces surprising results with numbers.
// String sorting — works as expected
const fruits = ["cherry", "apple", "banana"];
fruits.sort();
console.log(fruits); // ["apple", "banana", "cherry"]
// ❌ Number sorting — DEFAULT IS WRONG!
const numbers = [40, 100, 1, 5, 25];
numbers.sort();
console.log(numbers); // [1, 100, 25, 40, 5] — alphabetical, not numeric!
Custom Compare Functions
Pass a compare function to control the sort order. The function takes two elements (a, b) and returns:
- Negative →
acomes first - Positive →
bcomes first - Zero → keep original order
const numbers = [40, 100, 1, 5, 25];
// ✅ Ascending (smallest first)
numbers.sort((a, b) => a - b);
console.log(numbers); // [1, 5, 25, 40, 100]
// ✅ Descending (largest first)
numbers.sort((a, b) => b - a);
console.log(numbers); // [100, 40, 25, 5, 1]
Sorting Objects
const users = [
{ name: "Carol", age: 22 },
{ name: "Alice", age: 28 },
{ name: "Bob", age: 34 }
];
// Sort by age (ascending)
users.sort((a, b) => a.age - b.age);
console.log(users.map(u => u.name)); // ["Carol", "Alice", "Bob"]
// Sort by name (alphabetical)
users.sort((a, b) => a.name.localeCompare(b.name));
console.log(users.map(u => u.name)); // ["Alice", "Bob", "Carol"]
❌ sort() Mutates the Original Array
Unlike map and filter, sort() modifies the array in place. If you need the original order preserved, copy first.
const original = [3, 1, 4, 1, 5];
// ✅ Sort a copy, keep the original intact
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!
reverse()
const letters = ["a", "b", "c", "d"];
// Reverse in place (also mutates!)
letters.reverse();
console.log(letters); // ["d", "c", "b", "a"]
// Non-mutating reverse
const nums = [1, 2, 3];
const reversed = [...nums].reverse();
console.log(reversed); // [3, 2, 1]
console.log(nums); // [1, 2, 3] — unchanged
Method Chaining
Since map, filter, and sort return arrays, you can chain them together to build multi-step data pipelines. Each method's output feeds into the next.
const products = [
{ name: "Laptop", price: 999, category: "electronics" },
{ name: "Shirt", price: 29, category: "clothing" },
{ name: "Phone", price: 699, category: "electronics" },
{ name: "Pants", price: 49, category: "clothing" },
{ name: "Tablet", price: 449, category: "electronics" },
{ name: "Hat", price: 19, category: "clothing" }
];
// Pipeline: electronics → sorted by price → formatted strings
const result = products
.filter(p => p.category === "electronics") // Keep electronics
.sort((a, b) => a.price - b.price) // Sort cheapest first
.map(p => `${p.name}: $${p.price}`); // Format as strings
console.log(result);
// ["Tablet: $449", "Phone: $699", "Laptop: $999"]
(sorted)"] C -->|"map(format)"| D["3 strings"] style A fill:#fef3c7,stroke:#f59e0b,stroke-width:2px,color:#1e293b style B fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style C fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style D fill:#ecfdf5,stroke:#22c55e,stroke-width:2px,color:#1e293b
Chaining with reduce
// Total cost of all in-stock electronics
const totalElectronics = products
.filter(p => p.category === "electronics")
.reduce((sum, p) => sum + p.price, 0);
console.log(totalElectronics); // 2147
✅ Chain Order Matters
Put filter before map when possible — it reduces the number of elements that map has to process. Filter first, transform second.
// ✅ Better — filter first, then map (processes fewer elements)
products.filter(p => p.price > 100).map(p => p.name);
// ❌ Wasteful — maps everything, then filters
products.map(p => p.name).filter(name => /* can't access price anymore! */);
Choosing the Right Method
With so many options, here's how to pick the right method for the job.
| I Want To... | Use | Returns |
|---|---|---|
| Do something with each element | forEach() | undefined |
| Transform every element | map() | New array (same length) |
| Keep elements that match | filter() | New array (0 to same length) |
| Combine into one value | reduce() | Any single value |
| Find the first match | find() | Element or undefined |
| Check if any match | some() | true / false |
| Check if all match | every() | true / false |
| Reorder elements | sort() | Same array (mutated!) |
Hands-on Exercise
🏋️ Exercise: Product Catalog Pipeline
Objective: Use array methods to process product data — no manual loops allowed!
const products = [
{ id: 1, name: "Wireless Mouse", price: 29.99, category: "accessories", rating: 4.5, inStock: true },
{ id: 2, name: "Mechanical Keyboard", price: 89.99, category: "accessories", rating: 4.8, inStock: true },
{ id: 3, name: "USB-C Hub", price: 34.99, category: "accessories", rating: 4.2, inStock: false },
{ id: 4, name: "Monitor Stand", price: 49.99, category: "furniture", rating: 4.0, inStock: true },
{ id: 5, name: "Desk Lamp", price: 39.99, category: "furniture", rating: 4.6, inStock: true },
{ id: 6, name: "Webcam HD", price: 59.99, category: "electronics", rating: 4.3, inStock: false },
{ id: 7, name: "Headphones", price: 149.99, category: "electronics", rating: 4.7, inStock: true },
{ id: 8, name: "Mousepad XL", price: 14.99, category: "accessories", rating: 4.1, inStock: true }
];
// TODO: Solve each challenge using array methods (no for/while loops!)
// 1. Get an array of all product names
// Expected: ["Wireless Mouse", "Mechanical Keyboard", ...]
// 2. Get only in-stock products
// Expected: array of 6 product objects
// 3. Get in-stock accessories sorted by price (cheapest first)
// Expected: [Mousepad XL, Wireless Mouse, Mechanical Keyboard]
// 4. Calculate the total value of all in-stock products
// Expected: 374.94 (sum of prices where inStock is true)
// 5. Are all products rated 4.0 or higher?
// Expected: true
// 6. Create a "sale" version: in-stock products with 20% off,
// formatted as "Product Name — $XX.XX (was $XX.XX)"
// sorted by sale price ascending
💡 Hint
1: map extracting the name property.
2: filter checking inStock.
3: Chain filter → filter → sort.
4: filter → reduce with price accumulation.
5: every checking rating >= 4.0.
6: Chain filter → map (compute sale price) → sort → map (format string).
✅ Solution
// 1. All product names
const names = products.map(p => p.name);
console.log(names);
// 2. In-stock products
const inStock = products.filter(p => p.inStock);
console.log(inStock.length); // 6
// 3. In-stock accessories sorted by price
const accessories = products
.filter(p => p.inStock && p.category === "accessories")
.sort((a, b) => a.price - b.price);
console.log(accessories.map(p => p.name));
// ["Mousepad XL", "Wireless Mouse", "Mechanical Keyboard"]
// 4. Total value of in-stock products
const totalValue = products
.filter(p => p.inStock)
.reduce((sum, p) => sum + p.price, 0);
console.log(totalValue.toFixed(2)); // "374.94"
// 5. All rated 4.0+?
const allHighRated = products.every(p => p.rating >= 4.0);
console.log(allHighRated); // true
// 6. Sale version
const saleList = products
.filter(p => p.inStock)
.map(p => ({
...p,
salePrice: +(p.price * 0.80).toFixed(2)
}))
.sort((a, b) => a.salePrice - b.salePrice)
.map(p => `${p.name} — $${p.salePrice.toFixed(2)} (was $${p.price.toFixed(2)})`);
console.log(saleList);
// [
// "Mousepad XL — $11.99 (was $14.99)",
// "Wireless Mouse — $23.99 (was $29.99)",
// "Desk Lamp — $31.99 (was $39.99)",
// "Monitor Stand — $39.99 (was $49.99)",
// "Mechanical Keyboard — $71.99 (was $89.99)",
// "Headphones — $119.99 (was $149.99)"
// ]
🎯 Quick Quiz
Question 1: What does map() return?
Question 2: What does [1,2,3].filter(n => n > 1) return?
Question 3: What is the second argument to reduce()?
Question 4: Which method modifies the original array?
Summary
🎉 Key Takeaways
forEachruns code for each element but returns nothing — use for side effectsmaptransforms every element → new array, same lengthfilterkeeps matching elements → new array, fewer itemsreducecollapses to a single value — sums, counts, grouping, etc.somechecks if any element matches;everychecks if all matchsortorders in place — use(a, b) => a - bfor numbers,localeComparefor strings- Method chaining builds clean data pipelines:
filter→sort→map - Put
filterbeforemapfor better performance - Always provide an initial value for
reduce
📚 Additional Resources
🚀 What's Next?
You've now mastered arrays and their methods. The final lesson in this module covers strings and template literals — string methods for searching, slicing, and transforming text, plus template literal features you haven't seen yet.