Episode 1 — Fundamentals / 1.21 — Arrays
1.21.e -- Basic Iteration
In one sentence: You iterate arrays with the classic
forloop for full index control,for...offor clean value iteration, andwhileloops for conditional traversal -- while avoidingfor...inon 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
breakandcontinue
// 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:
| Loop | Iterates | Use for arrays? |
|---|---|---|
for | Index (number) | Yes -- full control |
for...of | Values | Yes -- cleanest for values |
for...in | Property 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.
| Feature | for | for...of | while | forEach |
|---|---|---|---|---|
| Index access | Yes | Via entries() | Yes | Yes (2nd arg) |
break | Yes | Yes | Yes | No |
continue | Yes | Yes | Yes | return (skip) |
| Async-friendly | Yes | Yes | Yes | No (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
forloop -- maximum control: index, break, continue, custom step.for...of-- cleanest syntax for iterating values; supports break/continue.for...in-- designed for objects, never use on arrays (string keys, prototype pollution, unordered).while-- best when iteration count is unknown or you are modifying the array during iteration.entries()gives[index, value]pairs insidefor...of.forEachcannotbreak-- usefororfor...ofwhen early exit is needed.- Iterate backwards when removing elements by index to avoid skipping.
Explain-It Challenge
Explain without notes:
- Name three problems with using
for...into iterate an array. - How would you iterate an array and get both the index and value using
for...of? - Why can you
breakout of afor...ofloop but not out offorEach? - When iterating an array to remove elements, why should you go backwards?
Navigation: <- 1.21.d -- Length Property . 1.21.f -- Practice Problems ->