Welcome Back, Code Cleaners!
In Part 4, we conquered async/await and error handling. Today, we're tackling something that separates junior developers from senior developers: mastering array methods and immutability.
I once reviewed code with 47 for-loops. Forty. Seven. The file was 800 lines of nested loops, mutated arrays, and index tracking variables. Refactoring it with array methods cut it down to 200 lines—75% less code that was 10x more readable.
Today's Mission:
- Replace loops with map, filter, reduce
- Master immutable array operations
- Avoid mutation bugs
- Chain array methods elegantly
- Know when NOT to use array methods
Let's transform your loops into clean, declarative code.
Practice 1: Use map() Instead of Transform Loops
The Problem: Manual loops for transforming data are verbose and error-prone.
❌ Bad: Manual Transformation Loop
const users = [
{ name: 'alice', age: 25 },
{ name: 'bob', age: 30 },
{ name: 'charlie', age: 35 }
];
// Manual loop - verbose and imperative
const names = [];
for (let i = 0; i < users.length; i++) {
names.push(users[i].name.toUpperCase());
}
// Transform prices
const prices = [10, 20, 30, 40];
const discounted = [];
for (let i = 0; i < prices.length; i++) {
discounted.push(prices[i] * 0.8);
}
✅ Good: map() Method
const users = [
{ name: 'alice', age: 25 },
{ name: 'bob', age: 30 },
{ name: 'charlie', age: 35 }
];
// Clean and declarative
const names = users.map(user => user.name.toUpperCase());
// ['ALICE', 'BOB', 'CHARLIE']
// Transform prices
const prices = [10, 20, 30, 40];
const discounted = prices.map(price => price * 0.8);
// [8, 16, 24, 32]
// Complex transformations
const userCards = users.map(user => ({
id: user.name,
displayName: `${user.name} (${user.age} years old)`,
isAdult: user.age >= 18
}));
When to Use map():
- Transforming each element into something new
- Creating new array from old array
- Extracting properties from objects
Practice 2: Use filter() Instead of Selection Loops
The Problem: Manual filtering loops are hard to read and maintain.
❌ Bad: Manual Filter Loop
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Get even numbers
const evens = [];
for (let i = 0; i < numbers.length; i++) {
if (numbers[i] % 2 === 0) {
evens.push(numbers[i]);
}
}
// Get adult users
const adults = [];
for (let i = 0; i < users.length; i++) {
if (users[i].age >= 18) {
adults.push(users[i]);
}
}
✅ Good: filter() Method
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Crystal clear intent
const evens = numbers.filter(num => num % 2 === 0);
// [2, 4, 6, 8, 10]
const odds = numbers.filter(num => num % 2 !== 0);
// [1, 3, 5, 7, 9]
// Filter objects
const adults = users.filter(user => user.age >= 18);
const premiumUsers = users.filter(user => {
return user.isPremium && user.age >= 18 && user.isActive;
});
// Complex filtering
const activeHighValueCustomers = customers.filter(customer =>
customer.status === 'active' &&
customer.totalSpent > 1000 &&
customer.lastPurchaseDate > thirtyDaysAgo
);
When to Use filter():
- Selecting elements that match criteria
- Removing unwanted elements
- Finding all matches (not just first)
Practice 3: Use reduce() for Aggregations
The Problem: Manual accumulation loops are hard to understand.
❌ Bad: Manual Accumulation
const numbers = [1, 2, 3, 4, 5];
// Calculate sum
let sum = 0;
for (let i = 0; i < numbers.length; i++) {
sum += numbers[i];
}
// Group by category
const products = [
{ name: 'Laptop', category: 'electronics', price: 999 },
{ name: 'Shirt', category: 'clothing', price: 29 },
{ name: 'Phone', category: 'electronics', price: 699 }
];
const grouped = {};
for (let i = 0; i < products.length; i++) {
const category = products[i].category;
if (!grouped[category]) {
grouped[category] = [];
}
grouped[category].push(products[i]);
}
✅ Good: reduce() Method
const numbers = [1, 2, 3, 4, 5];
// Sum - clear and concise
const sum = numbers.reduce((acc, num) => acc + num, 0);
// 15
// Average
const average = numbers.reduce((acc, num) => acc + num, 0) / numbers.length;
// 3
// Group by category
const products = [
{ name: 'Laptop', category: 'electronics', price: 999 },
{ name: 'Shirt', category: 'clothing', price: 29 },
{ name: 'Phone', category: 'electronics', price: 699 }
];
const grouped = products.reduce((acc, product) => {
const category = product.category;
if (!acc[category]) {
acc[category] = [];
}
acc[category].push(product);
return acc;
}, {});
// Count occurrences
const votes = ['yes', 'no', 'yes', 'yes', 'no'];
const counts = votes.reduce((acc, vote) => {
acc[vote] = (acc[vote] || 0) + 1;
return acc;
}, {});
// { yes: 3, no: 2 }
// Find max value
const prices = [10, 50, 30, 100, 20];
const maxPrice = prices.reduce((max, price) =>
price > max ? price : max
, 0);
// 100
When to Use reduce():
- Calculating single value from array (sum, average, max)
- Transforming array into object
- Grouping or counting
- Building complex data structures
Practice 4: Chain Array Methods Elegantly
The Problem: Nested loops for multi-step operations are hard to follow.
❌ Bad: Nested Loops
const users = [
{ name: 'Alice', age: 25, country: 'USA', active: true },
{ name: 'Bob', age: 17, country: 'UK', active: true },
{ name: 'Charlie', age: 30, country: 'USA', active: false },
{ name: 'Diana', age: 22, country: 'USA', active: true }
];
// Get average age of active adult users from USA
let filteredUsers = [];
for (let i = 0; i < users.length; i++) {
if (users[i].active && users[i].age >= 18 && users[i].country === 'USA') {
filteredUsers.push(users[i]);
}
}
let ages = [];
for (let i = 0; i < filteredUsers.length; i++) {
ages.push(filteredUsers[i].age);
}
let sum = 0;
for (let i = 0; i < ages.length; i++) {
sum += ages[i];
}
const average = sum / ages.length;
✅ Good: Method Chaining
const users = [
{ name: 'Alice', age: 25, country: 'USA', active: true },
{ name: 'Bob', age: 17, country: 'UK', active: true },
{ name: 'Charlie', age: 30, country: 'USA', active: false },
{ name: 'Diana', age: 22, country: 'USA', active: true }
];
// Clean pipeline - reads like a story
const averageAge = users
.filter(user => user.active)
.filter(user => user.age >= 18)
.filter(user => user.country === 'USA')
.map(user => user.age)
.reduce((sum, age, _, arr) => sum + age / arr.length, 0);
// Or with single filter for performance
const averageAge = users
.filter(user => user.active && user.age >= 18 && user.country === 'USA')
.map(user => user.age)
.reduce((sum, age, _, arr) => sum + age / arr.length, 0);
// Real-world example: Process orders
const totalRevenue = orders
.filter(order => order.status === 'completed')
.filter(order => order.date > startDate)
.map(order => order.total)
.reduce((sum, total) => sum + total, 0);
Best Practices:
- One operation per line for readability
- Combine filters when possible (performance)
- Use meaningful variable names in chains
Practice 5: Immutable Array Operations
The Problem: Mutating arrays causes bugs and makes code unpredictable.
❌ Bad: Mutation (Dangerous!)
const numbers = [1, 2, 3];
// These mutate the original array!
numbers.push(4); // numbers is now [1, 2, 3, 4]
numbers.pop(); // numbers is now [1, 2, 3]
numbers.shift(); // numbers is now [2, 3]
numbers.unshift(1); // numbers is now [1, 2, 3]
numbers.splice(1, 1); // numbers is now [1, 3]
numbers.sort(); // Mutates original!
numbers.reverse(); // Mutates original!
// Dangerous in React/Redux
function addTodo(todos, newTodo) {
todos.push(newTodo); // MUTATES original!
return todos;
}
✅ Good: Immutable Operations
const numbers = [1, 2, 3];
// These create new arrays (immutable)
const withAdded = [...numbers, 4]; // [1, 2, 3, 4]
const withoutLast = numbers.slice(0, -1); // [1, 2, 3]
const withoutFirst = numbers.slice(1); // [2, 3]
const withPrepended = [0, ...numbers]; // [0, 1, 2, 3]
const sorted = [...numbers].sort(); // [1, 2, 3] (copy then sort)
const reversed = [...numbers].reverse(); // [3, 2, 1] (copy then reverse)
// Remove item immutably
const removeAt = (arr, index) => [
...arr.slice(0, index),
...arr.slice(index + 1)
];
const without2nd = removeAt([1, 2, 3, 4], 1); // [1, 3, 4]
// Update item immutably
const updateAt = (arr, index, value) => [
...arr.slice(0, index),
value,
...arr.slice(index + 1)
];
const updated = updateAt([1, 2, 3], 1, 99); // [1, 99, 3]
// Or with map
const updated = numbers.map((num, i) => i === 1 ? 99 : num);
// Safe for React/Redux
function addTodo(todos, newTodo) {
return [...todos, newTodo]; // New array!
}
function removeTodo(todos, id) {
return todos.filter(todo => todo.id !== id);
}
function updateTodo(todos, id, updates) {
return todos.map(todo =>
todo.id === id ? { ...todo, ...updates } : todo
);
}
Why Immutability Matters:
- Predictable code (no side effects)
- Easier debugging (can track state changes)
- Essential for React/Redux
- Time-travel debugging possible
- Prevents accidental mutations
Practice 6: Use find() and some() for Searches
The Problem: Manual search loops are verbose.
❌ Bad: Manual Search Loops
// Find first match
let foundUser = null;
for (let i = 0; i < users.length; i++) {
if (users[i].id === 123) {
foundUser = users[i];
break;
}
}
// Check if any match exists
let hasAdmin = false;
for (let i = 0; i < users.length; i++) {
if (users[i].role === 'admin') {
hasAdmin = true;
break;
}
}
// Check if all match
let allAdults = true;
for (let i = 0; i < users.length; i++) {
if (users[i].age < 18) {
allAdults = false;
break;
}
}
✅ Good: find(), some(), every()
// Find first match
const foundUser = users.find(user => user.id === 123);
// Check if any match
const hasAdmin = users.some(user => user.role === 'admin');
// Check if all match
const allAdults = users.every(user => user.age >= 18);
// Find index
const index = users.findIndex(user => user.id === 123);
// Real-world examples
const outOfStockProduct = products.find(p => p.stock === 0);
const hasUnreadNotifications = notifications.some(n => !n.isRead);
const allOrdersShipped = orders.every(o => o.status === 'shipped');
Real-World Example: E-Commerce Dashboard
const orders = [
{ id: 1, customer: 'Alice', total: 150, status: 'completed', date: '2025-01-01' },
{ id: 2, customer: 'Bob', total: 80, status: 'completed', date: '2025-01-02' },
{ id: 3, customer: 'Alice', total: 200, status: 'pending', date: '2025-01-03' },
{ id: 4, customer: 'Charlie', total: 300, status: 'completed', date: '2025-01-01' }
];
// Calculate dashboard statistics
const stats = {
// Total revenue from completed orders
totalRevenue: orders
.filter(order => order.status === 'completed')
.reduce((sum, order) => sum + order.total, 0),
// Average order value
averageOrderValue: orders
.reduce((sum, order) => sum + order.total, 0) / orders.length,
// Top customers by spending
topCustomers: Object.entries(
orders.reduce((acc, order) => {
acc[order.customer] = (acc[order.customer] || 0) + order.total;
return acc;
}, {})
)
.map(([customer, total]) => ({ customer, total }))
.sort((a, b) => b.total - a.total)
.slice(0, 3),
// Orders by status
ordersByStatus: orders.reduce((acc, order) => {
acc[order.status] = (acc[order.status] || 0) + 1;
return acc;
}, {}),
// Has pending orders?
hasPending: orders.some(order => order.status === 'pending'),
// All orders completed?
allCompleted: orders.every(order => order.status === 'completed')
};
console.log(stats);
/*
{
totalRevenue: 530,
averageOrderValue: 182.5,
topCustomers: [
{ customer: 'Alice', total: 350 },
{ customer: 'Charlie', total: 300 },
{ customer: 'Bob', total: 80 }
],
ordersByStatus: { completed: 3, pending: 1 },
hasPending: true,
allCompleted: false
}
*/
When NOT to Use Array Methods
Performance Considerations:
❌ Bad: Multiple Passes (Slow for Large Arrays)
// This passes through array 3 times!
const result = data
.filter(x => x.active) // Pass 1
.map(x => x.value) // Pass 2
.reduce((sum, v) => sum + v, 0); // Pass 3
✅ Good: Single Pass with reduce()
// Single pass - 3x faster for large arrays
const result = data.reduce((sum, item) => {
return item.active ? sum + item.value : sum;
}, 0);
When to Use Loops Instead:
- Very large arrays (100k+ items) with multiple operations
- Early exit needed (break/return)
- Performance-critical code (game loops, animations)
Quick Wins Checklist for Part 5
Audit your array code:
✅ Are you using for-loops for transformations? (Use map)
✅ Are you using for-loops for filtering? (Use filter)
✅ Are you using for-loops for calculations? (Use reduce)
✅ Are you mutating arrays? (Use immutable operations)
✅ Are you chaining methods elegantly? (One per line)
✅ Are you using find/some/every? (Instead of manual search)
Part 5 Conclusion: Declarative > Imperative
Array methods transform imperative code (how to do it) into declarative code (what to do):
Before: "Loop through, check condition, push to new array"
After: "Filter active users"
The Mindset Shift:
- Stop thinking about HOW (loops, indices, variables)
- Start thinking about WHAT (filter, map, reduce)
Your code becomes self-documenting. users.filter(u => u.active) needs no comment—it explains itself.
Challenge: Find your most complex for-loop. Refactor it using array methods. Share the line count reduction! 📉
Coming Up in Part 6: Code Structure & Logic Flow 🚀
Next time, we'll master:
- Guard clauses to reduce nesting
- Early returns for clarity
- Avoiding deep conditionals
- Self-documenting code
- Writing code that reads like prose
Transform your nested if-else pyramids into clean, flat logic!
Ready to ditch for-loops? 👏 Clap for declarative code! (50 claps available)
Get notified of Part 6! 🔔 Follow me - Code structure drops in 3 days!
What's your favorite array method? 💬 Drop it in comments - map? filter? reduce?
Share the array mastery! 📤 Send this to developers still using for-loops - they'll thank you!
This is Part 5 of the 7-part "JavaScript Clean Code Mastery" series.
← Part 4: Async/Await & Error Handling | Part 6: Code Structure & Logic Flow →
Tags: #JavaScript #Arrays #Immutability #FunctionalProgramming #CleanCode #ArrayMethods #MapFilterReduce #WebDevelopment #Programming
Top comments (0)