Episode 1 — Fundamentals / 1.19 — Conditionals and Loops

1.19.c -- For Loop

In one sentence: The for loop is JavaScript's workhorse for counted iteration -- its three-part header (init; condition; update) gives precise control, while for...of iterates values and for...in iterates keys, each suited to different data structures.

Navigation: <-- 1.19.b -- Switch Statement . 1.19.d -- While and Do-While -->


1. Anatomy of the classic for loop

for (initialization; condition; update) {
  // loop body
}
PartPurposeExample
InitializationRuns once before the first iterationlet i = 0
ConditionChecked before each iteration; loop exits when falsyi < 5
UpdateRuns after each iteration body completesi++
for (let i = 0; i < 5; i++) {
  console.log(i);
}
// Output: 0, 1, 2, 3, 4

2. Loop execution flow (step by step)

1. Run initialization  ->  let i = 0
2. Check condition      ->  0 < 5? YES  ->  run body  ->  run update (i++)
3. Check condition      ->  1 < 5? YES  ->  run body  ->  run update (i++)
4. Check condition      ->  2 < 5? YES  ->  run body  ->  run update (i++)
5. Check condition      ->  3 < 5? YES  ->  run body  ->  run update (i++)
6. Check condition      ->  4 < 5? YES  ->  run body  ->  run update (i++)
7. Check condition      ->  5 < 5? NO   ->  EXIT LOOP

Key insight: The condition is tested before the body runs. If the condition is false initially, the body never executes:

for (let i = 10; i < 5; i++) {
  console.log(i);  // never runs
}

3. Iterating over arrays with index

The most common for loop pattern:

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

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

Reverse iteration

for (let i = fruits.length - 1; i >= 0; i--) {
  console.log(fruits[i]);
}
// date, cherry, banana, apple

Skipping by step

// Print even indices only
for (let i = 0; i < fruits.length; i += 2) {
  console.log(fruits[i]);
}
// apple, cherry

4. for...of -- iterating values

for...of works on any iterable: arrays, strings, Maps, Sets, NodeLists, and more.

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

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

Strings

for (const char of "Hello") {
  console.log(char);
}
// H, e, l, l, o

Maps

const prices = new Map([
  ["apple", 1.2],
  ["banana", 0.5],
  ["cherry", 2.0],
]);

for (const [fruit, price] of prices) {
  console.log(`${fruit}: $${price}`);
}
// apple: $1.2
// banana: $0.5
// cherry: $2.0

Sets

const uniqueNums = new Set([1, 2, 3, 2, 1]);

for (const num of uniqueNums) {
  console.log(num);
}
// 1, 2, 3

When you need the index with for...of

Use entries():

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

for (const [index, letter] of letters.entries()) {
  console.log(`${index}: ${letter}`);
}
// 0: a
// 1: b
// 2: c

5. for...in -- iterating keys (objects)

for...in iterates over enumerable property names (keys as strings):

const person = { name: "Alice", age: 30, city: "Paris" };

for (const key in person) {
  console.log(`${key}: ${person[key]}`);
}
// name: Alice
// age: 30
// city: Paris

Why NOT use for...in for arrays

for...in iterates all enumerable properties, including inherited ones and any properties added to Array.prototype. The keys are strings, not numbers.

const arr = [10, 20, 30];
arr.customProp = "oops";

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

Rule: Use for...of or classic for for arrays. Use for...in for plain objects.

Filtering own properties

for...in includes inherited enumerable properties. Use hasOwnProperty to filter:

for (const key in person) {
  if (person.hasOwnProperty(key)) {
    console.log(key, person[key]);
  }
}

Or use Object.keys() / Object.entries() with for...of for a cleaner approach:

for (const [key, value] of Object.entries(person)) {
  console.log(`${key}: ${value}`);
}

6. Comparison table

LoopBest forGives youWorks with
for (let i...)Counted iteration, need indexIndex + element via arr[i]Anything indexable
for...ofIterating valuesEach value directlyArrays, strings, Maps, Sets, iterables
for...inIterating keys of objectsEach key as a stringObjects (avoid on arrays)

7. Nested loops

When you need to process two-dimensional data (matrices, grids, combinations):

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

for (let row = 0; row < matrix.length; row++) {
  for (let col = 0; col < matrix[row].length; col++) {
    process.stdout.write(String(matrix[row][col]) + " ");
  }
  console.log();  // newline after each row
}
// 1 2 3
// 4 5 6
// 7 8 9

Finding all pairs

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

for (let i = 0; i < items.length; i++) {
  for (let j = i + 1; j < items.length; j++) {
    console.log(items[i], items[j]);
  }
}
// a b
// a c
// b c

Multiplication table

for (let i = 1; i <= 5; i++) {
  let row = "";
  for (let j = 1; j <= 5; j++) {
    row += String(i * j).padStart(4);
  }
  console.log(row);
}
//    1   2   3   4   5
//    2   4   6   8  10
//    3   6   9  12  15
//    4   8  12  16  20
//    5  10  15  20  25

Performance note: Nested loops run in O(n * m) time. For large datasets, consider whether you can eliminate the inner loop with a Set lookup or hash map.


8. Loop performance considerations

TipExplanation
Cache lengthfor (let i = 0, len = arr.length; i < len; i++) avoids re-reading .length on each iteration (modern engines optimize this, but it is good practice for very large arrays)
Avoid DOM queries in loopCalling document.querySelector inside a loop is expensive; query once before the loop
Prefer for...of for readabilityUnless you need the index, for...of is cleaner and less error-prone
Break earlyIf you only need the first match, use break to stop iterating

9. let vs var in for loops (the closure trap)

This is a classic interview topic. With var, the loop variable is function-scoped (shared across all iterations). With let, each iteration gets its own block-scoped copy.

// var -- all callbacks share the SAME `i`
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Output: 3, 3, 3  (not 0, 1, 2!)

// let -- each iteration gets its OWN `i`
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Output: 0, 1, 2  (correct!)

Why? With var, by the time setTimeout fires, the loop has finished and i is 3. With let, each iteration creates a new binding, so the closure captures the current value.

The old IIFE workaround (pre-ES6)

Before let existed, developers used an IIFE to capture the value:

for (var i = 0; i < 3; i++) {
  (function(j) {
    setTimeout(() => console.log(j), 100);
  })(i);
}
// Output: 0, 1, 2

Modern advice: Always use let in for loops. There is no reason to use var here.


10. Advanced patterns

Omitting parts of the for header

All three parts are optional:

// Omit initialization (declared earlier)
let i = 0;
for (; i < 5; i++) { console.log(i); }

// Omit update (done inside body)
for (let i = 0; i < 5; ) {
  console.log(i);
  i += 2;
}

// Infinite loop (omit everything)
// for (;;) { ... }  // must break or return inside

Multiple variables

for (let left = 0, right = 9; left < right; left++, right--) {
  console.log(left, right);
}
// 0 9
// 1 8
// 2 7
// 3 6
// 4 5

11. Real-world examples

Example 1 -- Sum of array

function sum(numbers) {
  let total = 0;
  for (const num of numbers) {
    total += num;
  }
  return total;
}

console.log(sum([10, 20, 30, 40]));  // 100

Example 2 -- Search in array

function findUser(users, id) {
  for (const user of users) {
    if (user.id === id) {
      return user;  // found -- exit immediately
    }
  }
  return null;  // not found
}

Example 3 -- Building a string

function repeat(str, times) {
  let result = "";
  for (let i = 0; i < times; i++) {
    result += str;
  }
  return result;
}

console.log(repeat("ha", 3));  // "hahaha"

Example 4 -- Counting occurrences

function countChar(str, target) {
  let count = 0;
  for (const char of str) {
    if (char === target) {
      count++;
    }
  }
  return count;
}

console.log(countChar("mississippi", "s"));  // 4

Key takeaways

  1. The classic for loop has three parts: init, condition (checked before each iteration), and update (runs after each iteration).
  2. for...of iterates values of iterables -- use it for arrays, strings, Maps, Sets.
  3. for...in iterates keys -- use it for plain objects only, never arrays.
  4. Nested loops enable matrix traversal but watch for O(n*m) performance.
  5. Always use let (not var) in for loops to avoid the closure trap.

Explain-It Challenge

Explain without notes:

  1. Walk through the execution of for (let i = 2; i >= 0; i--) step by step.
  2. Why does for...in on an array give you string keys and possibly extra properties?
  3. What happens with var in a for loop + setTimeout, and how does let fix it?

Navigation: <-- 1.19.b -- Switch Statement . 1.19.d -- While and Do-While -->