Episode 1 — Fundamentals / 1.21 — Arrays

1.21.e -- Basic Iteration

In one sentence: You iterate arrays with the classic for loop for full index control, for...of for clean value iteration, and while loops for conditional traversal -- while avoiding for...in on arrays because it iterates property keys (including inherited ones) as strings.

Navigation: <- 1.21.d -- Length Property . 1.21.f -- Practice Problems ->


1. Classic for loop with index

The for loop gives you full control: you have the index, can skip, break, or step in any direction.

const fruits = ["apple", "banana", "cherry", "date", "elderberry"];

for (let i = 0; i < fruits.length; i++) {
  console.log(`${i}: ${fruits[i]}`);
}
// 0: apple
// 1: banana
// 2: cherry
// 3: date
// 4: elderberry

Accessing both index and value is natural:

const scores = [85, 92, 78, 95, 88];
let total = 0;

for (let i = 0; i < scores.length; i++) {
  total += scores[i];
  console.log(`Score ${i + 1}: ${scores[i]} | Running total: ${total}`);
}
console.log(`Average: ${total / scores.length}`);
// Average: 87.6

Skipping every other element:

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

// Step by 2
for (let i = 0; i < letters.length; i += 2) {
  console.log(letters[i]);
}
// a, c, e

Breaking early:

const items = [3, 7, 2, 9, 4, 6];

// Find first value > 5
for (let i = 0; i < items.length; i++) {
  if (items[i] > 5) {
    console.log(`Found ${items[i]} at index ${i}`);
    break;  // stop immediately
  }
}
// Found 7 at index 1

Continuing (skip current iteration):

const numbers = [1, -2, 3, -4, 5, -6];

// Sum only positive numbers
let sum = 0;
for (let i = 0; i < numbers.length; i++) {
  if (numbers[i] < 0) continue;  // skip negative
  sum += numbers[i];
}
console.log(sum);  // 9

2. Caching length for performance

In extremely hot loops, you can cache length to avoid repeated property lookups:

const bigArray = Array.from({ length: 100000 }, (_, i) => i);

// Standard -- modern engines optimize this well
for (let i = 0; i < bigArray.length; i++) {
  // ...
}

// Cached -- marginal gain in rare cases
for (let i = 0, len = bigArray.length; i < len; i++) {
  // ...
}

In practice: Modern JavaScript engines (V8, SpiderMonkey) already optimize arr.length in loop conditions. Caching is a micro-optimization you rarely need, but it is good to recognize in existing codebases.


3. for...of loop -- iterating values

for...of (ES6) iterates over the values of an iterable (arrays, strings, Maps, Sets).

const colors = ["red", "green", "blue", "yellow"];

for (const color of colors) {
  console.log(color);
}
// red
// green
// blue
// yellow

Advantages of for...of:

  • Cleaner syntax when you only need values
  • Works with any iterable, not just arrays
  • Supports break and continue
// Breaking out of for...of
const prices = [5.99, 12.50, 3.25, 25.00, 8.75];

for (const price of prices) {
  if (price > 20) {
    console.log(`Expensive item found: $${price}`);
    break;
  }
}
// Expensive item found: $25

Limitation: You do not get the index directly.

// If you need the index, use a separate counter...
let idx = 0;
for (const color of colors) {
  console.log(`${idx}: ${color}`);
  idx++;
}

// ...or use entries() (Section 6)

4. for...in loop -- and why it is BAD for arrays

for...in is designed for iterating object properties (keys). Using it on arrays is problematic.

const arr = ["a", "b", "c"];

for (const key in arr) {
  console.log(key, typeof key);
}
// "0" string
// "1" string
// "2" string

Problem 1: Indices are strings, not numbers.

for (const key in arr) {
  console.log(key + 1);  // "01", "11", "21" -- string concatenation!
}

Problem 2: Iterates inherited and custom properties.

const arr = ["a", "b", "c"];
arr.customProp = "oops";

for (const key in arr) {
  console.log(key);
}
// "0", "1", "2", "customProp"  -- customProp appears!

Problem 3: Order is not guaranteed.

The spec does not guarantee for...in iterates in numeric order for all engines in all edge cases (though modern engines typically do for integer indices).

Problem 4: It picks up prototype modifications.

// If someone modifies Array.prototype (bad practice, but libraries do it)
Array.prototype.myMethod = function() {};

const arr = [1, 2, 3];
for (const key in arr) {
  console.log(key);
}
// "0", "1", "2", "myMethod"  -- prototype pollution!

Summary:

LoopIteratesUse for arrays?
forIndex (number)Yes -- full control
for...ofValuesYes -- cleanest for values
for...inProperty keys (strings)No -- use for plain objects only

5. while loop iteration

while loops are useful when you do not know the number of iterations in advance, or when you are processing/removing elements.

// Basic while iteration
const tasks = ["email", "meeting", "code review", "lunch", "deploy"];
let i = 0;

while (i < tasks.length) {
  console.log(`Task: ${tasks[i]}`);
  i++;
}

Processing until empty:

const queue = ["Alice", "Bob", "Carol", "Diana"];

while (queue.length > 0) {
  const person = queue.shift();
  console.log(`Serving: ${person} | Remaining: ${queue.length}`);
}
// Serving: Alice | Remaining: 3
// Serving: Bob | Remaining: 2
// Serving: Carol | Remaining: 1
// Serving: Diana | Remaining: 0

do...while variant -- runs at least once:

const arr = [42];
let j = 0;

do {
  console.log(arr[j]);
  j++;
} while (j < arr.length);
// 42

Removing elements while iterating (backwards):

const nums = [1, 2, 3, 4, 5, 6, 7, 8];

// Remove even numbers -- iterate backwards to avoid index shifting
let k = nums.length - 1;
while (k >= 0) {
  if (nums[k] % 2 === 0) {
    nums.splice(k, 1);
  }
  k--;
}
console.log(nums);  // [1, 3, 5, 7]

6. Getting index with for...of using entries()

arr.entries() returns an iterator of [index, value] pairs, giving you the best of both worlds.

const fruits = ["apple", "banana", "cherry", "date"];

for (const [index, fruit] of fruits.entries()) {
  console.log(`${index}: ${fruit}`);
}
// 0: apple
// 1: banana
// 2: cherry
// 3: date

Related iterators:

const arr = ["a", "b", "c"];

// keys() -- indices only
for (const idx of arr.keys()) {
  console.log(idx);
}
// 0, 1, 2

// values() -- values only (same as for...of on the array)
for (const val of arr.values()) {
  console.log(val);
}
// "a", "b", "c"

// entries() -- both
for (const [i, v] of arr.entries()) {
  console.log(i, v);
}
// 0 "a"
// 1 "b"
// 2 "c"

Practical example -- numbered list with conditional formatting:

const students = ["Alice", "Bob", "Carol", "Diana", "Eve"];

for (const [i, name] of students.entries()) {
  const position = i + 1;
  const label = position <= 3 ? "(top 3)" : "";
  console.log(`${position}. ${name} ${label}`);
}
// 1. Alice (top 3)
// 2. Bob (top 3)
// 3. Carol (top 3)
// 4. Diana
// 5. Eve

7. Breaking out of loops vs forEach (preview)

forEach() is an array method for iteration (covered in 1.22 -- Array Methods). A key difference: you cannot break out of forEach.

const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// for loop -- can break
for (let i = 0; i < nums.length; i++) {
  if (nums[i] > 5) break;
  console.log(nums[i]);
}
// 1, 2, 3, 4, 5

// for...of -- can break
for (const n of nums) {
  if (n > 5) break;
  console.log(n);
}
// 1, 2, 3, 4, 5

// forEach -- CANNOT break (no break/continue allowed)
nums.forEach(n => {
  // if (n > 5) break;  // SyntaxError: Illegal break statement
  console.log(n);
});
// Prints ALL 10 numbers -- no early exit

Workaround for forEach (not recommended):

// You can throw an exception, but this is an anti-pattern
// You can use `return` to skip the current iteration (like continue)
nums.forEach(n => {
  if (n > 5) return;  // acts like "continue", NOT "break"
  console.log(n);
});
// 1, 2, 3, 4, 5 (but still iterates 6-10 silently)

Rule: If you need break, use for, for...of, or while. Use forEach only when you know you will process every element.

Featureforfor...ofwhileforEach
Index accessYesVia entries()YesYes (2nd arg)
breakYesYesYesNo
continueYesYesYesreturn (skip)
Async-friendlyYesYesYesNo (does not await)

8. Iterating backwards

Sometimes you need to process elements from last to first.

const arr = [10, 20, 30, 40, 50];

// Method 1: for loop counting down
for (let i = arr.length - 1; i >= 0; i--) {
  console.log(arr[i]);
}
// 50, 40, 30, 20, 10

// Method 2: while loop counting down
let i = arr.length;
while (i--) {
  console.log(arr[i]);
}
// 50, 40, 30, 20, 10

// Method 3: for...of on reversed copy (non-mutating)
for (const val of [...arr].reverse()) {
  console.log(val);
}
// 50, 40, 30, 20, 10

When backwards iteration is important:

// Removing elements by index -- go backwards to avoid shifting issues
const items = ["keep", "remove", "keep", "remove", "keep"];

for (let i = items.length - 1; i >= 0; i--) {
  if (items[i] === "remove") {
    items.splice(i, 1);
  }
}
console.log(items);  // ["keep", "keep", "keep"]

// If you iterate forwards while removing, indices shift and you skip elements!

9. Real examples

Displaying a list of items

const todoItems = [
  { text: "Buy groceries", done: false },
  { text: "Walk the dog",  done: true },
  { text: "Write report",  done: false },
  { text: "Call dentist",   done: true }
];

console.log("=== TODO LIST ===");
for (const [i, item] of todoItems.entries()) {
  const status = item.done ? "[x]" : "[ ]";
  console.log(`${i + 1}. ${status} ${item.text}`);
}
// 1. [ ] Buy groceries
// 2. [x] Walk the dog
// 3. [ ] Write report
// 4. [x] Call dentist

Calculating totals

const cart = [
  { name: "Widget",  price: 9.99,  qty: 2 },
  { name: "Gadget",  price: 24.99, qty: 1 },
  { name: "Gizmo",   price: 4.50,  qty: 5 }
];

let grandTotal = 0;
for (const item of cart) {
  const lineTotal = item.price * item.qty;
  grandTotal += lineTotal;
  console.log(`${item.name}: $${lineTotal.toFixed(2)}`);
}
console.log(`Grand total: $${grandTotal.toFixed(2)}`);
// Widget: $19.98
// Gadget: $24.99
// Gizmo: $22.50
// Grand total: $67.47

Linear search

function linearSearch(arr, target) {
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] === target) {
      return i;  // found -- return index
    }
  }
  return -1;  // not found
}

const data = [15, 42, 8, 73, 29, 56];
console.log(linearSearch(data, 73));   // 3
console.log(linearSearch(data, 100));  // -1

Building a frequency counter

const words = ["the", "cat", "sat", "on", "the", "mat", "the", "cat"];
const frequency = {};

for (const word of words) {
  if (frequency[word]) {
    frequency[word]++;
  } else {
    frequency[word] = 1;
  }
}

console.log(frequency);
// { the: 3, cat: 2, sat: 1, on: 1, mat: 1 }

Comparing two arrays element by element

function arraysEqual(a, b) {
  if (a.length !== b.length) return false;

  for (let i = 0; i < a.length; i++) {
    if (a[i] !== b[i]) return false;
  }

  return true;
}

console.log(arraysEqual([1, 2, 3], [1, 2, 3]));  // true
console.log(arraysEqual([1, 2, 3], [1, 2, 4]));  // false
console.log(arraysEqual([1, 2], [1, 2, 3]));      // false

Key takeaways

  1. for loop -- maximum control: index, break, continue, custom step.
  2. for...of -- cleanest syntax for iterating values; supports break/continue.
  3. for...in -- designed for objects, never use on arrays (string keys, prototype pollution, unordered).
  4. while -- best when iteration count is unknown or you are modifying the array during iteration.
  5. entries() gives [index, value] pairs inside for...of.
  6. forEach cannot break -- use for or for...of when early exit is needed.
  7. Iterate backwards when removing elements by index to avoid skipping.

Explain-It Challenge

Explain without notes:

  1. Name three problems with using for...in to iterate an array.
  2. How would you iterate an array and get both the index and value using for...of?
  3. Why can you break out of a for...of loop but not out of forEach?
  4. When iterating an array to remove elements, why should you go backwards?

Navigation: <- 1.21.d -- Length Property . 1.21.f -- Practice Problems ->