DEV Community

sizan mahmud0
sizan mahmud0

Posted on

JavaScript Clean Code Mastery: Part 5 - Array Methods and Immutability That Transform Your Code

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);
}
Enter fullscreen mode Exit fullscreen mode

✅ 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
}));
Enter fullscreen mode Exit fullscreen mode

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]);
  }
}
Enter fullscreen mode Exit fullscreen mode

✅ 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
);
Enter fullscreen mode Exit fullscreen mode

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]);
}
Enter fullscreen mode Exit fullscreen mode

✅ 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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

✅ 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);
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

✅ 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
  );
}
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

✅ 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');
Enter fullscreen mode Exit fullscreen mode

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
}
*/
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

✅ 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);
Enter fullscreen mode Exit fullscreen mode

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)