Episode 1 — Fundamentals / 1.19 — Conditionals and Loops
1.19.c -- For Loop
In one sentence: The
forloop is JavaScript's workhorse for counted iteration -- its three-part header (init; condition; update) gives precise control, whilefor...ofiterates values andfor...initerates 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
}
| Part | Purpose | Example |
|---|---|---|
| Initialization | Runs once before the first iteration | let i = 0 |
| Condition | Checked before each iteration; loop exits when falsy | i < 5 |
| Update | Runs after each iteration body completes | i++ |
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
| Loop | Best for | Gives you | Works with |
|---|---|---|---|
for (let i...) | Counted iteration, need index | Index + element via arr[i] | Anything indexable |
for...of | Iterating values | Each value directly | Arrays, strings, Maps, Sets, iterables |
for...in | Iterating keys of objects | Each key as a string | Objects (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
| Tip | Explanation |
|---|---|
Cache length | for (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 loop | Calling document.querySelector inside a loop is expensive; query once before the loop |
Prefer for...of for readability | Unless you need the index, for...of is cleaner and less error-prone |
| Break early | If 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
- The classic
forloop has three parts: init, condition (checked before each iteration), and update (runs after each iteration). for...ofiterates values of iterables -- use it for arrays, strings, Maps, Sets.for...initerates keys -- use it for plain objects only, never arrays.- Nested loops enable matrix traversal but watch for O(n*m) performance.
- Always use
let(notvar) in for loops to avoid the closure trap.
Explain-It Challenge
Explain without notes:
- Walk through the execution of
for (let i = 2; i >= 0; i--)step by step. - Why does
for...inon an array give you string keys and possibly extra properties? - What happens with
varin a for loop +setTimeout, and how doesletfix it?
Navigation: <-- 1.19.b -- Switch Statement . 1.19.d -- While and Do-While -->