Skip to main content

⚡ 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() and some()
  • 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.

graph LR A["Original Array"] --> B["map()"] A --> C["filter()"] A --> D["reduce()"] B --> E["New Array
(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 shouted = names.forEach(n => n.toUpperCase());
console.log(shouted);  // 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.400000000000006, 43.2]
// (floating-point rounding — format with toFixed(2) for display)

// 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, 78, 91]  (42, 67 and 55 are left out)

// 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
                
graph LR A["[85, 42, 93, 67, 78]"] --> B["filter: keep if ≥ 70"] B --> C["85 ✅"] B --> D["42 ❌"] B --> E["93 ✅"] B --> F["67 ❌"] B --> G["78 ✅"] C --> H["[85, 93, 78]"] E --> H G --> H style A fill:#fef3c7,stroke:#f59e0b,stroke-width:2px,color:#1e293b style H fill:#ecfdf5,stroke:#22c55e,stroke-width:2px,color:#1e293b style D fill:#fef2f2,stroke:#ef4444,stroke-width:2px,color:#1e293b style F fill:#fef2f2,stroke:#ef4444,stroke-width:2px,color:#1e293b

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
                
graph LR A["Initial: 0"] --> B["+ 10 = 10"] B --> C["+ 20 = 30"] C --> D["+ 30 = 60"] D --> E["+ 40 = 100"] style A fill:#fef3c7,stroke:#f59e0b,stroke-width:2px,color:#1e293b style E fill:#ecfdf5,stroke:#22c55e,stroke-width:2px,color:#1e293b

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 undefinedWhat's the first match?
findIndex(fn)Index or -1Where's the first match?
some(fn)true / falseDoes any element match?
every(fn)true / falseDo 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 → a comes first
  • Positive → b comes 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, use toSorted() (supported in all modern browsers) or copy first.


const original = [3, 1, 4, 1, 5];

// ✅ toSorted() returns a sorted COPY
const sorted = original.toSorted((a, b) => a - b);
console.log(sorted);    // [1, 1, 3, 4, 5]
console.log(original);  // [3, 1, 4, 1, 5] — unchanged!

// ✅ The older way: spread into a copy, then sort the copy
const sorted2 = [...original].sort((a, b) => a - b);
                    

reverse()


const letters = ["a", "b", "c", "d"];

// Reverse in place (also mutates!)
letters.reverse();
console.log(letters);  // ["d", "c", "b", "a"]

// Non-mutating reverse — toReversed() (or [...nums].reverse())
const nums = [1, 2, 3];
const reversed = nums.toReversed();
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"]
                
graph LR A["6 products"] -->|"filter(electronics)"| B["3 products"] B -->|"sort(by price)"| C["3 products
(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 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);

// ❌ Backwards — after map() you only have name strings,
//    so the price you wanted to filter on is gone!
// products.map(p => p.name).filter(name => ???);
                    

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 elementforEach()undefined
Transform every elementmap()New array (same length)
Keep elements that matchfilter()New array (0 to same length)
Combine into one valuereduce()Any single value
Find the first matchfind()Element or undefined
Check if any matchsome()true / false
Check if all matchevery()true / false
Reorder elementssort()Same array (mutated!)
graph TD A["What do I need?"] --> B{"A new array?"} B -->|"Yes, same length"| C["map()"] B -->|"Yes, fewer items"| D["filter()"] B -->|"No"| E{"A single value?"} E -->|"One element"| F["find()"] E -->|"A sum/count/object"| G["reduce()"] E -->|"true/false"| H{"All or any?"} H -->|"Any match?"| I["some()"] H -->|"All match?"| J["every()"] E -->|"Just run code"| K["forEach()"] style C fill:#ecfdf5,stroke:#22c55e,stroke-width:2px,color:#1e293b style D fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style G fill:#fce7f3,stroke:#ec4899,stroke-width:2px,color:#1e293b

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"  (raw value is 374.94000000000005)

// 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?

📓 Learning Journal

Take five minutes to add to your learning journal. Write down:

  • Key concepts you learned
  • What clicked — the idea or technique that made sense
  • Questions or confusion, so you know what to revisit
  • Ideas to try on your own pages
  • How you feel about your progress

✍️ This lesson's prompt: Pick a small dataset you care about (your movies, workouts, or expenses) and write, in plain English, a filter → sort → map pipeline you'd run on it. Why does filter usually come before map? Which method — probably reduce? — are you still unsure about?

Summary

🎉 Key Takeaways

  • forEach runs code for each element but returns nothing — use for side effects
  • map transforms every element → new array, same length
  • filter keeps matching elements → new array, fewer items
  • reduce collapses to a single value — sums, counts, grouping, etc.
  • some checks if any element matches; every checks if all match
  • sort orders in place — use (a, b) => a - b for numbers, localeCompare for strings
  • Method chaining builds clean data pipelines: filter → sort → map
  • Put filter before map for 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.