Chapter 1 of ?
js 21 min read

JavaScript Mastery — Chapter 13: JS Array Iteration & Modern Methods

MODULE 2: OBJECTS, ARRAYS & COLLECTIONS ES6+ FUNCTIONAL CORE

Chapter 13: JS Array Iteration & Modern Methods

Master functional array programming in JavaScript — from declarative transformations with map & filter to accumulator reduction, search heuristics, quantifiers, and high-performance method chaining.

14 Core Sections 2 Fullscreen SVG Architecture Diagrams 3 Interactive Mini-Labs 1 Enterprise Coding Challenge 4 Quiz Questions

13.1 Imperative vs. Declarative Array Processing

Before ES5 and ES6, processing array elements meant writing imperative for loops. In imperative programming, you tell the JavaScript engine how to step through every index, increment counter variables, boundary-check lengths, and manually accumulate results into temporary mutable state.

Modern JavaScript embraces declarative functional iteration. Instead of micro-managing indices, you pass a callback function that declares what should happen to each item. The engine handles memory navigation under the hood.

Characteristic Imperative (for / while) Declarative (map / filter / reduce)
Focus How to iterate (index incrementation, termination checks) What transformation or test to apply
State Mutability Frequently mutates external variables or push buffers Pure, immutable transformations that return new arrays
Off-By-One Errors High risk (i <= length vs i < length) Zero risk (engine handles boundary conditions)
Early Termination Supported directly via break and continue Handled via dedicated methods (find, some)
Readability & Chaining Cluttered, deeply nested loops Composable, chainable data pipelines

Code Comparison: Squaring Numbers

// ❌ Imperative: Manual index counter & mutating push buffer
const numbers = [1, 2, 3, 4, 5];
const squaredImperative = [];
for (let i = 0; i < numbers.length; i++) {
  squaredImperative.push(numbers[i] ** 2);
}

// ✅ Declarative: Pure 1-line transformation (immutable)
const squaredDeclarative = numbers.map(num => num ** 2);
console.log(squaredDeclarative); // [1, 4, 9, 16, 25]
Try it Yourself »

13.2 forEach() — Iteration, Callbacks & The "No Break" Gotcha

The forEach() method executes a provided callback function once for each array element in ascending index order. It always returns undefined and does not produce a new array.

Callback Signature

Every standard array iteration callback receives three positional parameters:

arr.forEach((currentValue, index, array) => {
  // currentValue: the element currently being processed
  // index: the zero-based index of currentValue
  // array: the original array forEach was called upon
});
The forEach "No Break" Gotcha

There is no way to stop or break a forEach() loop except by throwing an exception. A return statement inside the callback merely exits the current callback invocation (acting like a continue in a traditional loop), but does NOT terminate the iteration!

const items = [10, 20, 30, 40];
items.forEach(val => {
  if (val === 30) return; // ⚠️ Does NOT stop the loop!
  console.log(val);       // Prints: 10, 20, 40
});

// ✅ If you need early termination with break, use for...of:
for (const val of items) {
  if (val === 30) break;  // Stops completely
}

forEach() in Action

const fruits = ["Apple", "Banana", "Cherry", "Mango"];

// forEach executes a callback for each element; always returns undefined
fruits.forEach((fruit, index) => {
  console.log(`Index ${index}: ${fruit}`);
});
Try it Yourself »

13.3 map() — The 1:1 Immutable Transformation Pipeline

map() creates a brand new array populated with the results of calling a provided function on every element in the calling array.

  • Guaranteed Length: The output array always has the exact same length as the input array.
  • Non-Mutating: The original array remains completely unmodified.
  • Pure Functions: The callback should ideally be a pure function without side-effects.

Example: Transforming Raw User Data to View Models

const users = [
  { id: 101, firstName: "Ada", lastName: "Lovelace", score: 98 },
  { id: 102, firstName: "Alan", lastName: "Turing", score: 95 },
  { id: 103, firstName: "Grace", lastName: "Hopper", score: 99 }
];

// Map objects to formatted display strings
const leaderboard = users.map((u, i) => `#${i + 1} ${u.firstName} ${u.lastName} (${u.score} pts)`);
console.log(leaderboard);
// ["#1 Ada Lovelace (98 pts)", "#2 Alan Turing (95 pts)", "#3 Grace Hopper (99 pts)"]
Try it Yourself »
Common Pitfall: Forgetting to Return in map()

If you omit the return keyword in a block-body arrow function, JavaScript returns undefined by default, giving you an array filled with [undefined, undefined, ...]. Always return an explicit value or use concise arrow body syntax arr.map(x => x * 2).

13.4 filter() — The Boolean Predicate Selector

The filter() method creates a shallow copy of a portion of a given array, filtered down to just the elements that pass the test implemented by the provided predicate function.

If the callback returns a truthy value, the element is included in the new array. If it returns falsy, the element is omitted. If no elements pass, it returns an empty array [].

Example: Filtering by Criteria & The `filter(Boolean)` Trick

const inventory = [
  { name: "Laptop", price: 999, inStock: true },
  { name: "Keyboard", price: 79, inStock: false },
  { name: "Monitor", price: 299, inStock: true },
  { name: "Mouse", price: 39, inStock: false }
];

// Keep only affordable products currently in stock
const availableAffordable = inventory.filter(item => item.inStock && item.price < 300);
console.log(availableAffordable);
// [{ name: "Monitor", price: 299, inStock: true }]

// 💡 Pro Tip: Filter out all falsy values (null, undefined, 0, "", false) in one line:
const mixed = ["Alice", "", null, "Bob", undefined, 0, "Carol"];
const clean = mixed.filter(Boolean);
console.log(clean); // ["Alice", "Bob", "Carol"]
Try it Yourself »

13.5 Architecture Diagram 1: The Map-Filter Data Pipeline

The declarative power of modern JavaScript stems from chaining pure methods. In this visual pipeline, a raw stream of data passes through a filter gate (which rejects elements not meeting criteria) and then through a map transformation stage.

Architecture Diagram 1: The Map-Filter Data Pipeline
1. INPUT ARRAY (4 items) P1: Price $1200 · Stock 0 P2: Price $80 · Stock 5 P3: Price $450 · Stock 3 P4: Price $25 · Stock 12 2. .filter(p => stock > 0 && price < 100) ❌ P1: out of stock (rejected) ✓ P2: $80 < $100 (PASSED) ❌ P3: $450 > $100 (rejected) ✓ P4: $25 < $100 (PASSED) 3. .map(p => `$${p.price.toFixed(2)}`) Result[0]: "$80.00" Formatted price string Result[1]: "$25.00" Formatted price string

13.6 reduce() & reduceRight() — The Swiss Army Accumulator

reduce() executes a user-supplied reducer callback on each element of the array, passing in the return value from the calculation on the preceding element. The final result is a single accumulated value (which can be a number, string, object, array, or map).

Signature & Parameters
const result = arr.reduce((accumulator, currentValue, currentIndex, array) => {
  // return new accumulator value for the next iteration
  return accumulator + currentValue;
}, initialValue);
The initialValue Trap

Always provide an initialValue! If you omit it:

  • accumulator is automatically initialized to arr[0], and iteration begins at index 1.
  • If the array is empty and no initialValue is provided, JavaScript throws: TypeError: Reduce of empty array with no initial value!
Four Essential Reduce Patterns
1. Running Total & Averages
const prices = [19.99, 4.99, 9.99];
const total = prices.reduce((acc, p) => acc + p, 0);
console.log(total); // 34.97
2. Grouping by Property
const pets = [{t:'dog'}, {t:'cat'}, {t:'dog'}];
const byType = pets.reduce((acc, p) => {
  acc[p.t] = (acc[p.t] || 0) + 1;
  return acc;
}, {});
console.log(byType); // { dog: 2, cat: 1 }
3. Index Lookup Dictionary
const users = [{id: 'u1', n: 'Ada'}, {id: 'u2', n: 'Alan'}];
const userMap = users.reduce((acc, u) => {
  acc[u.id] = u;
  return acc;
}, {});
console.log(userMap['u1'].n); // "Ada" (O(1) lookup)
4. Flattening Nested Arrays
const matrix = [[1, 2], [3, 4], [5]];
const flat = matrix.reduce((acc, row) => acc.concat(row), []);
console.log(flat); // [1, 2, 3, 4, 5]

reduce() in Action

const cart = [
  { item: "Keyboard", price: 120, category: "Hardware" },
  { item: "Course Subscription", price: 49, category: "Software" },
  { item: "Mousepad", price: 15, category: "Hardware" }
];

// Summing prices with initialValue: 0
const total = cart.reduce((acc, curr) => acc + curr.price, 0);
console.log("Total:", total); // 184
Try it Yourself »

13.7 Architecture Diagram 2: The Reduce Accumulator State Machine

Visualize how the accumulator acts as an active register passing state forward across sequential steps. At each clock-tick iteration, acc consumes the currentValue and produces the next state.

Architecture Diagram 2: Reduce Accumulator State Machine Lifecycle
initial 0 ITERATION 0 acc in: 0 curr: 15 acc out: 15 ITERATION 1 acc in: 15 curr: 30 acc out: 45 ITERATION 2 acc in: 45 curr: 45 acc out: 90 FINAL RESULT 100

13.8 Search Heuristics: find(), findIndex(), findLast()

When searching for specific elements or objects by custom logic, JavaScript offers dedicated short-circuiting search methods that stop inspecting elements the moment a match is discovered.

Forward Search (Head to Tail)
  • find(predicate): Returns the first element value that satisfies the condition, or undefined if none found.
  • findIndex(predicate): Returns the zero-based index of the first match, or -1 if not found.
const users = [{id: 1, v: false}, {id: 2, v: true}, {id: 3, v: true}];
const firstActive = users.find(u => u.v); // {id: 2, v: true}
const firstActiveIdx = users.findIndex(u => u.v); // 1
Reverse Search (Tail to Head) ES2023
  • findLast(predicate): Scans backwards from the end and returns the last matching element.
  • findLastIndex(predicate): Returns the index of the last match without needing to clone or mutate with reverse()!
const lastActive = users.findLast(u => u.v); // {id: 3, v: true}
const lastActiveIdx = users.findLastIndex(u => u.v); // 2

find(), findIndex(), and findLast() in Action

const users = [
  { id: 101, name: "Alice", role: "Dev", active: false },
  { id: 102, name: "Bob", role: "Lead", active: true },
  { id: 103, name: "Charlie", role: "Dev", active: true },
  { id: 104, name: "Diana", role: "Lead", active: true }
];

const firstLead = users.find(u => u.role === "Lead");
const latestLead = users.findLast(u => u.role === "Lead");
console.log("First Lead:", firstLead.name);
console.log("Latest Lead:", latestLead.name);
Try it Yourself »

13.9 Quantifiers: some() & every()

Quantifiers test assertions across an array and return a boolean. Both methods feature short-circuit optimization: they halt evaluation immediately once the outcome is mathematically decided.

some() — Existential Check (∃)

Returns true if at least one element passes the test. Short-circuits immediately on the first true.

const scores = [65, 82, 94, 58];
const hasHonor = scores.some(s => s >= 90); // true (stops at 94)
const hasFail = scores.some(s => s < 50);   // false
every() — Universal Check (∀)

Returns true if all elements pass the test. Short-circuits immediately on the first false.

const allPassed = scores.every(s => s >= 50); // true
const allAStudents = scores.every(s => s >= 90); // false (stops at 65!)
The Empty Array Mathematical Quirk (Vacuous Truth)

What happens when you run [].every(fn) on an empty array? It returns true! This follows the logical principle of vacuous truth: there exists no counter-example in an empty set that violates the condition. Conversely, [].some(fn) returns false because no element exists to satisfy it.

some() and every() Quantifiers

const scores = [72, 85, 91, 64, 88];

const allPassed = scores.every(s => s >= 50);      // true
const hasDistinction = scores.some(s => s >= 90);  // true
console.log({ allPassed, hasDistinction });
Try it Yourself »

13.10 Modern Array Utilities: flat(), flatMap() & Array.from()

1. flat(depth) & flatMap(fn) ES2019

flat() creates a new array with all sub-array elements concatenated into it recursively up to the specified depth. Use Infinity to flatten arbitrary depth. flatMap() maps each element and flattens the result by 1 level in a single efficient pass.

const nested = [1, [2, [3, [4]]]];
console.log(nested.flat(2));        // [1, 2, 3, [4]]
console.log(nested.flat(Infinity)); // [1, 2, 3, 4]

// flatMap(): Splitting sentences into individual words
const sentences = ["Hello modern world", "JavaScript is awesome"];
const words = sentences.flatMap(s => s.split(" "));
console.log(words);
// ["Hello", "modern", "world", "JavaScript", "is", "awesome"]
2. Array.from(arrayLike, mapFn)

Creates a real Array from an array-like object (NodeList, arguments, strings, Sets) while optionally applying an inline map function:

// Generate sequential number range 1 to 5
const range = Array.from({ length: 5 }, (_, i) => i + 1);
console.log(range); // [1, 2, 3, 4, 5]

// Convert string to uppercase character array
const letters = Array.from("code", char => char.toUpperCase());
console.log(letters); // ["C", "O", "D", "E"]

flat(), flatMap(), and Array.from() in Action

const nested = [1, [2, [3, [4]]]];
console.log(nested.flat(Infinity)); // [1, 2, 3, 4]

const sentences = ["JavaScript arrays", "functional programming"];
const words = sentences.flatMap(s => s.split(" "));
console.log(words); // ["JavaScript", "arrays", "functional", "programming"]
Try it Yourself »

13.11 Method Chaining & Performance Trade-offs

Because filter() and map() return new array instances, you can chain them into readable pipelines. However, in performance-critical loops with tens of thousands of items, each chained method allocates an intermediate garbage-collected array.

Declarative Method Chain

High readability, perfect for small to medium datasets (< 50k items).

const totalTax = orders
  .filter(o => o.status === "completed") // intermediate arr 1
  .map(o => o.amount * 0.08)             // intermediate arr 2
  .reduce((sum, tax) => sum + tax, 0);
Single-Pass Reduce Optimization

Zero intermediate arrays allocated. Performs filtering and calculation in one pass.

const totalTaxOptimized = orders.reduce((sum, o) => {
  if (o.status === "completed") {
    return sum + (o.amount * 0.08);
  }
  return sum;
}, 0);

Method Chaining in Action

const orders = [
  { id: "A", amount: 150, status: "completed" },
  { id: "B", amount: 45, status: "pending" },
  { id: "C", amount: 200, status: "completed" },
  { id: "D", amount: 90, status: "completed" }
];

const grandTotal = orders
  .filter(o => o.status === "completed")
  .map(o => o.amount * 1.10)
  .reduce((acc, curr) => acc + curr, 0);

console.log("Grand Total with Tax:", grandTotal); // 484
Try it Yourself »

13.12 Interactive Mini-Labs

LAB 1
Reactive E-Commerce Pipeline Simulator

Adjust category and price filters to watch the declarative filter() & map() pipeline re-evaluate dynamically.

// Live Generated Pipeline Output:
LAB 2
Interactive Step-by-Step Reduce Visualizer

Step through an accumulator sum [15, 30, 45, 10].reduce((acc, curr) => acc + curr, 0) cycle-by-cycle to inspect internal registers.

Initial State (before execution)
ARRAY: [15, 30, 45, 10] INITIAL VALUE: 0
LAB 3
Quantifier Predicate Inspector: some() vs every()

Modify student exam scores below to watch every(>= 70) and some(>= 95) update in real-time.

scores.every(s => s >= 70)
scores.some(s => s >= 95)

13.13 Coding Challenge: Enterprise Inventory & Sales Aggregator

Write a function aggregateTransactions(transactions) that processes an array of e-commerce order objects:

  1. Filter out all cancelled orders (status !== "cancelled").
  2. Calculate the total gross revenue (sum of all amount).
  3. Calculate the 10% VAT tax payable on the total.
  4. Group total revenue by category into an object { electronics: X, apparel: Y, ... }.

function aggregateTransactions(transactions) {
  // Step 1: Filter out cancelled transactions
  const activeOrders = transactions.filter(t => t.status !== "cancelled");

  // Step 2 & 4: Calculate total & group by category using reduce in a single pass
  const summary = activeOrders.reduce((acc, order) => {
    // Accumulate total gross revenue
    acc.grossRevenue += order.amount;

    // Accumulate category subtotal
    acc.byCategory[order.category] = (acc.byCategory[order.category] || 0) + order.amount;

    return acc;
  }, { grossRevenue: 0, byCategory: {} });

  // Step 3: Compute VAT tax (10%)
  const taxPayable = Number((summary.grossRevenue * 0.10).toFixed(2));

  return {
    orderCount: activeOrders.length,
    grossRevenue: Number(summary.grossRevenue.toFixed(2)),
    taxPayable: taxPayable,
    categoryBreakdown: summary.byCategory
  };
}

// ── Sample Test ──
const sampleOrders = [
  { id: 1, category: "tech", amount: 250, status: "completed" },
  { id: 2, category: "apparel", amount: 80, status: "completed" },
  { id: 3, category: "tech", amount: 150, status: "cancelled" },
  { id: 4, category: "apparel", amount: 120, status: "completed" }
];

console.log(aggregateTransactions(sampleOrders));
/* Output:
{
  orderCount: 3,
  grossRevenue: 450,
  taxPayable: 45,
  categoryBreakdown: { tech: 250, apparel: 200 }
}
*/
Try it Yourself »

Chapter 13 — Key Takeaways

  • forEach() returns undefined; return does not break the loop
  • map() guarantees 1:1 length output; never mutates original array
  • filter() expects boolean predicate; use filter(Boolean) to strip falsy items
  • reduce() accumulates array to a single value; always pass initialValue
  • reduceRight() iterates from tail to head (useful for pipeline composition)
  • find() stops on first match; findLast() searches from tail (ES2023)
  • some() short-circuits on 1st truthy; every() on 1st falsy
  • [].every(...) === true (vacuous truth); [].some(...) === false
  • flatMap() maps and flattens depth-1 in a single memory pass
  • Next Chapter: Set, Map, WeakSet, and WeakMap collections!
Done with this chapter?
Mark it complete to track your progress and unlock your certificate.
Next Up

Learner Reviews

Write a Review
Share your experience to help other learners.
Your Rating *