Array Iteration: forEach, map, filter, reduce
Introduction to Array Iteration Methods
JavaScript arrays come with a powerful set of built-in methods that let you iterate over elements, transform data, and extract meaningful results -- all without writing traditional for loops. These methods are called higher-order functions because they accept other functions as arguments. Mastering them is essential for writing clean, readable, and maintainable JavaScript code. In modern JavaScript development, array iteration methods are the backbone of data processing, whether you are filtering products in an e-commerce application, calculating totals in a shopping cart, or transforming API responses into a format your UI components can render.
Before these methods existed, developers relied on for loops and manual index tracking to iterate over arrays. While for loops still work perfectly fine, the functional iteration methods offer significant advantages: they are more expressive, they reduce the chance of off-by-one errors, and they encourage immutable data patterns that make your code easier to reason about. Each method serves a specific purpose, and understanding when to use which method is a skill that separates beginner JavaScript developers from intermediate ones.
The forEach Method
The forEach method executes a provided function once for each element in the array. It is the simplest iteration method and serves as a direct replacement for a basic for loop. Unlike the other methods we will cover, forEach does not return a new array -- it simply runs a function for each element and returns undefined.
Basic Syntax
The forEach method takes a callback function that receives up to three arguments: the current element, the current index, and the entire array being iterated.
Example: forEach Basic Syntax
const fruits = ['apple', 'banana', 'cherry', 'date'];
// Basic usage -- just the element
fruits.forEach(function(fruit) {
console.log(fruit);
});
// Output: apple, banana, cherry, date
// With arrow function syntax
fruits.forEach(fruit => console.log(fruit));
// Using index parameter
fruits.forEach(function(fruit, index) {
console.log(index + ': ' + fruit);
});
// Output: 0: apple, 1: banana, 2: cherry, 3: date
// Using all three parameters
fruits.forEach(function(fruit, index, array) {
console.log(fruit + ' is item ' + (index + 1) + ' of ' + array.length);
});
// Output: apple is item 1 of 4, banana is item 2 of 4, ...
When to Use forEach
Use forEach when you need to perform a side effect for each element -- such as logging, updating the DOM, sending analytics events, or pushing values into an external data structure. It is important to understand that forEach always returns undefined, so you cannot chain it with other array methods. If you need to transform data or produce a new array, use map instead.
Example: forEach for DOM Updates
const menuItems = ['Home', 'About', 'Services', 'Contact'];
const navList = document.querySelector('#nav-list');
menuItems.forEach(function(item) {
const li = document.createElement('li');
li.textContent = item;
navList.appendChild(li);
});
forEach loop early. If you need to stop iteration when a condition is met, use a regular for loop, for...of, or the some or every methods instead.The map Method
The map method creates a new array by calling a provided function on every element in the original array. The new array will always have the same length as the original array. This is the go-to method for transforming data -- taking an array of values in one shape and producing an array of values in another shape.
Basic Syntax and Transformation
Example: map Transformations
const numbers = [1, 2, 3, 4, 5];
// Double each number
const doubled = numbers.map(function(num) {
return num * 2;
});
console.log(doubled); // [2, 4, 6, 8, 10]
console.log(numbers); // [1, 2, 3, 4, 5] -- original unchanged
// Convert to strings
const strings = numbers.map(num => String(num));
console.log(strings); // ['1', '2', '3', '4', '5']
// Extract specific properties from objects
const users = [
{ name: 'Alice', age: 30 },
{ name: 'Bob', age: 25 },
{ name: 'Charlie', age: 35 }
];
const names = users.map(user => user.name);
console.log(names); // ['Alice', 'Bob', 'Charlie']
Transforming Objects with map
One of the most common uses of map in real-world applications is transforming arrays of objects. You might receive data from an API in one format and need to reshape it for your UI components.
Example: Reshaping API Data with map
// Data from an API
const apiResponse = [
{ id: 1, first_name: 'Jane', last_name: 'Doe', email_address: 'jane@example.com' },
{ id: 2, first_name: 'John', last_name: 'Smith', email_address: 'john@example.com' },
{ id: 3, first_name: 'Sara', last_name: 'Lee', email_address: 'sara@example.com' }
];
// Transform into the format your UI needs
const uiUsers = apiResponse.map(function(person) {
return {
userId: person.id,
fullName: person.first_name + ' ' + person.last_name,
email: person.email_address,
initials: person.first_name[0] + person.last_name[0]
};
});
console.log(uiUsers);
// [
// { userId: 1, fullName: 'Jane Doe', email: 'jane@example.com', initials: 'JD' },
// { userId: 2, fullName: 'John Smith', email: 'john@example.com', initials: 'JS' },
// { userId: 3, fullName: 'Sara Lee', email: 'sara@example.com', initials: 'SL' }
// ]
return statement inside your map callback. If you forget to return a value, the resulting array will be filled with undefined values. With arrow functions using implicit return (no curly braces), the return is automatic, but with a function body (curly braces), you must explicitly write return.The filter Method
The filter method creates a new array containing only the elements that pass a test implemented by the provided function. The callback function must return a truthy or falsy value for each element. Elements where the callback returns a truthy value are included in the new array; elements where it returns a falsy value are excluded. The original array remains unchanged.
Basic Filtering
Example: filter Basics
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Get only even numbers
const evens = numbers.filter(function(num) {
return num % 2 === 0;
});
console.log(evens); // [2, 4, 6, 8, 10]
// Get numbers greater than 5
const greaterThanFive = numbers.filter(num => num > 5);
console.log(greaterThanFive); // [6, 7, 8, 9, 10]
// Filter strings by length
const words = ['cat', 'elephant', 'dog', 'hippopotamus', 'ant'];
const longWords = words.filter(word => word.length > 4);
console.log(longWords); // ['elephant', 'hippopotamus']
Filtering Objects
In real-world applications, you will frequently filter arrays of objects based on one or more conditions. This is especially common in e-commerce product listings, search results, and data dashboards.
Example: Filtering Products
const products = [
{ name: 'Laptop', price: 999, category: 'electronics', inStock: true },
{ name: 'Shirt', price: 29, category: 'clothing', inStock: true },
{ name: 'Headphones', price: 199, category: 'electronics', inStock: false },
{ name: 'Book', price: 15, category: 'books', inStock: true },
{ name: 'Monitor', price: 449, category: 'electronics', inStock: true },
{ name: 'Jeans', price: 59, category: 'clothing', inStock: false }
];
// Get only electronics that are in stock
const availableElectronics = products.filter(function(product) {
return product.category === 'electronics' && product.inStock;
});
console.log(availableElectronics);
// [{ name: 'Laptop', ... }, { name: 'Monitor', ... }]
// Get affordable products (under $50) that are available
const affordableAvailable = products.filter(function(product) {
return product.price < 50 && product.inStock;
});
console.log(affordableAvailable);
// [{ name: 'Shirt', ... }, { name: 'Book', ... }]
filter method can return an empty array if no elements match the condition. Always account for this in your code -- for example, check the length of the filtered array before trying to access its elements.The reduce Method
The reduce method is the most powerful and versatile of all array iteration methods. It executes a reducer callback function on each element of the array, passing the return value from one iteration to the next, ultimately reducing the entire array down to a single accumulated value. That value can be a number, a string, an object, another array, or any other data type.
Basic Syntax
The reduce method takes two arguments: a reducer callback function and an optional initial value. The reducer callback receives four parameters: the accumulator (the accumulated result from previous iterations), the current element, the current index, and the source array. Always provide an initial value to avoid unexpected behavior with empty arrays.
Example: reduce Basics -- Summing Numbers
const numbers = [10, 20, 30, 40, 50];
// Sum all numbers
const total = numbers.reduce(function(accumulator, currentValue) {
return accumulator + currentValue;
}, 0);
console.log(total); // 150
// How it works step by step:
// Step 1: accumulator = 0 (initial), current = 10 => returns 10
// Step 2: accumulator = 10, current = 20 => returns 30
// Step 3: accumulator = 30, current = 30 => returns 60
// Step 4: accumulator = 60, current = 40 => returns 100
// Step 5: accumulator = 100, current = 50 => returns 150
// With arrow function
const sum = numbers.reduce((acc, num) => acc + num, 0);
Practical Uses of reduce
Beyond simple summation, reduce can build objects, flatten arrays, count occurrences, find maximum values, and perform virtually any accumulation operation.
Example: Shopping Cart Total with reduce
const cart = [
{ name: 'Widget', price: 25.99, quantity: 3 },
{ name: 'Gadget', price: 49.99, quantity: 1 },
{ name: 'Doohickey', price: 9.99, quantity: 5 }
];
// Calculate total cost
const cartTotal = cart.reduce(function(total, item) {
return total + (item.price * item.quantity);
}, 0);
console.log(cartTotal.toFixed(2)); // '177.91'
// Count total items in cart
const totalItems = cart.reduce(function(count, item) {
return count + item.quantity;
}, 0);
console.log(totalItems); // 9
Example: Counting Occurrences with reduce
const fruits = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple'];
const fruitCounts = fruits.reduce(function(counts, fruit) {
counts[fruit] = (counts[fruit] || 0) + 1;
return counts;
}, {});
console.log(fruitCounts);
// { apple: 3, banana: 2, cherry: 1 }
Example: Grouping Data with reduce
const people = [
{ name: 'Alice', department: 'Engineering' },
{ name: 'Bob', department: 'Marketing' },
{ name: 'Charlie', department: 'Engineering' },
{ name: 'Diana', department: 'Marketing' },
{ name: 'Eve', department: 'Sales' }
];
const grouped = people.reduce(function(groups, person) {
const dept = person.department;
if (!groups[dept]) {
groups[dept] = [];
}
groups[dept].push(person.name);
return groups;
}, {});
console.log(grouped);
// {
// Engineering: ['Alice', 'Charlie'],
// Marketing: ['Bob', 'Diana'],
// Sales: ['Eve']
// }
reduce. Without an initial value, reduce uses the first element of the array as the initial accumulator, which can cause errors with empty arrays and unexpected behavior when reducing arrays of objects. Always provide an explicit initial value.The find and findIndex Methods
While filter returns all matching elements, sometimes you only need the first element that matches a condition. The find method returns the first element in the array that satisfies the provided testing function. If no element matches, it returns undefined. The findIndex method works similarly but returns the index of the first matching element, or -1 if no match is found.
Example: find and findIndex
const users = [
{ id: 1, name: 'Alice', role: 'admin' },
{ id: 2, name: 'Bob', role: 'user' },
{ id: 3, name: 'Charlie', role: 'moderator' },
{ id: 4, name: 'Diana', role: 'user' }
];
// Find the first admin
const admin = users.find(function(user) {
return user.role === 'admin';
});
console.log(admin); // { id: 1, name: 'Alice', role: 'admin' }
// Find user by ID
const user = users.find(u => u.id === 3);
console.log(user.name); // 'Charlie'
// Find the index of a specific user
const bobIndex = users.findIndex(u => u.name === 'Bob');
console.log(bobIndex); // 1
// When nothing is found
const superAdmin = users.find(u => u.role === 'superadmin');
console.log(superAdmin); // undefined
const missingIndex = users.findIndex(u => u.name === 'Zara');
console.log(missingIndex); // -1
The some and every Methods
The some method tests whether at least one element in the array passes the provided test. It returns true as soon as it finds a matching element and stops iterating. The every method tests whether all elements pass the test. It returns false as soon as it finds an element that does not match. Both methods return a boolean value.
Example: some and every
const scores = [85, 92, 78, 95, 88];
// Did anyone score above 90?
const hasHighScore = scores.some(score => score > 90);
console.log(hasHighScore); // true
// Did everyone pass (score above 70)?
const allPassed = scores.every(score => score > 70);
console.log(allPassed); // true
// Did everyone score above 80?
const allAbove80 = scores.every(score => score > 80);
console.log(allAbove80); // false (78 fails)
// Practical example: form validation
const formFields = [
{ name: 'email', value: 'user@example.com', valid: true },
{ name: 'password', value: 'secret123', valid: true },
{ name: 'age', value: '', valid: false }
];
const isFormValid = formFields.every(field => field.valid);
console.log(isFormValid); // false
const hasAnyInput = formFields.some(field => field.value.length > 0);
console.log(hasAnyInput); // true
some and every use short-circuit evaluation. The some method stops as soon as it finds a true result, and every stops as soon as it finds a false result. This makes them more efficient than iterating through the entire array when an early answer is possible.Chaining Array Methods
One of the greatest strengths of these array methods is that they can be chained together. Since map and filter return new arrays, you can call another array method immediately on the result. This lets you build complex data processing pipelines that are both powerful and readable.
Example: Chaining filter, map, and reduce
const orders = [
{ product: 'Laptop', amount: 999, status: 'completed' },
{ product: 'Mouse', amount: 29, status: 'completed' },
{ product: 'Keyboard', amount: 79, status: 'cancelled' },
{ product: 'Monitor', amount: 449, status: 'completed' },
{ product: 'Webcam', amount: 89, status: 'pending' },
{ product: 'Desk', amount: 299, status: 'completed' }
];
// Calculate total revenue from completed orders
const totalRevenue = orders
.filter(order => order.status === 'completed')
.reduce((sum, order) => sum + order.amount, 0);
console.log(totalRevenue); // 1776
// Get names of completed orders over $100, formatted
const bigCompletedOrders = orders
.filter(order => order.status === 'completed')
.filter(order => order.amount > 100)
.map(order => order.product + ' ($' + order.amount + ')');
console.log(bigCompletedOrders);
// ['Laptop ($999)', 'Monitor ($449)', 'Desk ($299)']
Example: Complex Data Pipeline
const employees = [
{ name: 'Alice', department: 'Engineering', salary: 95000, active: true },
{ name: 'Bob', department: 'Marketing', salary: 72000, active: true },
{ name: 'Charlie', department: 'Engineering', salary: 88000, active: false },
{ name: 'Diana', department: 'Engineering', salary: 102000, active: true },
{ name: 'Eve', department: 'Marketing', salary: 68000, active: true },
{ name: 'Frank', department: 'Sales', salary: 78000, active: true }
];
// Get average salary of active engineering employees
const activeEngineers = employees
.filter(emp => emp.active && emp.department === 'Engineering');
const avgSalary = activeEngineers
.reduce((sum, emp) => sum + emp.salary, 0) / activeEngineers.length;
console.log(avgSalary); // 98500
// Create a department summary
const departmentSummary = employees
.filter(emp => emp.active)
.reduce(function(summary, emp) {
if (!summary[emp.department]) {
summary[emp.department] = { count: 0, totalSalary: 0 };
}
summary[emp.department].count += 1;
summary[emp.department].totalSalary += emp.salary;
return summary;
}, {});
console.log(departmentSummary);
// {
// Engineering: { count: 2, totalSalary: 197000 },
// Marketing: { count: 2, totalSalary: 140000 },
// Sales: { count: 1, totalSalary: 78000 }
// }
Performance Considerations
While array iteration methods are elegant and readable, it is important to understand their performance characteristics, especially when working with large datasets.
- Chaining creates intermediate arrays -- When you chain
filter().map(), thefiltermethod creates a temporary array thatmapthen iterates over. For very large arrays (hundreds of thousands of elements), this can consume extra memory. In such cases, consider using a singlereduceto perform both operations in one pass. - forEach vs for loop -- A traditional
forloop is slightly faster thanforEachbecause it avoids the overhead of function calls. However, the difference is negligible for most applications. Prioritize readability unless you are dealing with performance-critical code processing millions of elements. - Early termination -- Methods like
find,findIndex,some, andeverystop iterating as soon as a result is determined. Use them instead offilterwhen you only need the first match or a boolean answer. - Avoid mutations -- Never modify the original array inside a
map,filter, orforEachcallback. This leads to unpredictable bugs. Always treat the source array as read-only and build new data structures instead.
Example: Optimizing with a Single reduce Instead of Chaining
const data = [
{ value: 10, active: true },
{ value: 20, active: false },
{ value: 30, active: true },
{ value: 40, active: true },
{ value: 50, active: false }
];
// Less efficient: two passes through the data
const result1 = data
.filter(item => item.active)
.map(item => item.value * 2);
// More efficient: single pass
const result2 = data.reduce(function(acc, item) {
if (item.active) {
acc.push(item.value * 2);
}
return acc;
}, []);
// Both produce [20, 60, 80]
Real-World Data Processing Examples
Let us put everything together with comprehensive examples that mirror real-world scenarios you will encounter in professional JavaScript development.
Example: E-Commerce Product Filtering and Display
const products = [
{ id: 1, name: 'Wireless Mouse', price: 29.99, rating: 4.5, category: 'accessories', inStock: true },
{ id: 2, name: 'Mechanical Keyboard', price: 89.99, rating: 4.8, category: 'accessories', inStock: true },
{ id: 3, name: 'USB Hub', price: 19.99, rating: 3.9, category: 'accessories', inStock: false },
{ id: 4, name: 'Laptop Stand', price: 45.99, rating: 4.2, category: 'furniture', inStock: true },
{ id: 5, name: 'Monitor Light', price: 59.99, rating: 4.7, category: 'lighting', inStock: true },
{ id: 6, name: 'Desk Pad', price: 24.99, rating: 4.1, category: 'accessories', inStock: true }
];
// 1. Get in-stock accessories sorted by rating
const topAccessories = products
.filter(p => p.category === 'accessories' && p.inStock)
.sort((a, b) => b.rating - a.rating)
.map(p => p.name + ' - $' + p.price + ' (Rating: ' + p.rating + ')');
console.log(topAccessories);
// ['Mechanical Keyboard - $89.99 (Rating: 4.8)',
// 'Wireless Mouse - $29.99 (Rating: 4.5)',
// 'Desk Pad - $24.99 (Rating: 4.1)']
// 2. Calculate average price of in-stock products
const inStockProducts = products.filter(p => p.inStock);
const avgPrice = inStockProducts
.reduce((sum, p) => sum + p.price, 0) / inStockProducts.length;
console.log('Average price: $' + avgPrice.toFixed(2)); // Average price: $50.19
// 3. Check if any product needs restocking
const needsRestock = products.some(p => !p.inStock);
console.log('Needs restock:', needsRestock); // true
// 4. Group products by category with counts
const categoryStats = products.reduce(function(stats, product) {
const cat = product.category;
if (!stats[cat]) {
stats[cat] = { total: 0, inStock: 0, avgRating: 0, ratings: [] };
}
stats[cat].total += 1;
if (product.inStock) stats[cat].inStock += 1;
stats[cat].ratings.push(product.rating);
return stats;
}, {});
// Calculate average ratings per category
Object.keys(categoryStats).forEach(function(cat) {
const ratings = categoryStats[cat].ratings;
categoryStats[cat].avgRating = ratings.reduce((s, r) => s + r, 0) / ratings.length;
delete categoryStats[cat].ratings;
});
console.log(categoryStats);
Example: Transforming API Response Data
// Simulated API response
const apiData = {
status: 'success',
results: [
{ user_id: 101, first_name: 'Ahmad', last_name: 'Hassan',
email: 'ahmad@example.com', created_at: '2024-01-15', orders_count: 12 },
{ user_id: 102, first_name: 'Sara', last_name: 'Ali',
email: 'sara@example.com', created_at: '2024-03-22', orders_count: 0 },
{ user_id: 103, first_name: 'Omar', last_name: 'Khan',
email: 'omar@example.com', created_at: '2023-11-08', orders_count: 45 },
{ user_id: 104, first_name: 'Layla', last_name: 'Mahmoud',
email: 'layla@example.com', created_at: '2024-06-01', orders_count: 7 }
]
};
// Transform into UI-friendly format for active customers only
const activeCustomers = apiData.results
.filter(user => user.orders_count > 0)
.map(function(user) {
return {
id: user.user_id,
displayName: user.first_name + ' ' + user.last_name,
email: user.email,
memberSince: new Date(user.created_at).toLocaleDateString(),
isVIP: user.orders_count > 10,
tier: user.orders_count > 20 ? 'gold' : user.orders_count > 10 ? 'silver' : 'bronze'
};
});
console.log(activeCustomers);
// Summary statistics
const stats = apiData.results.reduce(function(acc, user) {
acc.totalOrders += user.orders_count;
acc.activeUsers += user.orders_count > 0 ? 1 : 0;
return acc;
}, { totalOrders: 0, activeUsers: 0 });
console.log('Total orders: ' + stats.totalOrders); // 64
console.log('Active users: ' + stats.activeUsers); // 3
Practice Exercise
Create an array of at least 8 student objects, each with properties for name, grade (a number from 0-100), subject (one of 'math', 'science', 'english'), and enrolled (boolean). Then complete these tasks using array iteration methods:
- Use
filterto get all enrolled students with a grade above 75. - Use
mapto create an array of strings in the format "Name: grade% (subject)". - Use
reduceto calculate the average grade across all enrolled students. - Use
findto locate the first student in the math subject with the highest grade. - Use
someto check if any student is failing (grade below 50). - Use
everyto check if all enrolled students have a grade above 30. - Chain
filterandreduceto find the total grade points for enrolled science students. - Use
reduceto group students by subject and count how many are enrolled in each.