The Classic for Loop
The traditional for loop gives you full control with three parts: initializer, condition, and update expression.
// Syntax: for (init; condition; update) { body }
for (let i = 0; i < 5; i++) {
console.log("Iteration " + i);
}
// Count down
for (let i = 10; i >= 0; i -= 2) {
console.log(i);
}
// Iterate an array by index
const colors = ["red", "green", "blue"];
for (let i = 0; i < colors.length; i++) {
console.log(i + ": " + colors[i]);
}
For large arrays, cache arr.length in a variable to avoid re-evaluating it each iteration: for (let i = 0, len = arr.length; i < len; i++). Modern engines optimize this anyway, but it is good practice.
for...of Loop
Introduced in ES6, for...of iterates over any iterable object β arrays, strings, Sets, Maps, NodeLists, generators, and more. It gives you the value directly, without an index.
// Array
const fruits = ["apple", "banana", "cherry"];
for (const fruit of fruits) {
console.log(fruit);
}
// String β iterates characters
for (const char of "Hello") {
process.stdout.write(char + " ");
}
console.log();
// Set β only unique values
const unique = new Set([1, 2, 2, 3, 3]);
for (const val of unique) {
console.log(val);
}
// Map β [key, value] pairs via destructuring
const scores = new Map([["Alice", 95], ["Bob", 87]]);
for (const [name, score] of scores) {
console.log(name + ": " + score);
}
for...in Loop
for...in iterates over the enumerable property keys of an object. It is designed for plain objects, not arrays.
const person = { name: "Alice", age: 30, city: "London" };
for (const key in person) {
console.log(key + ": " + person[key]);
}
for...in also enumerates inherited properties. Use hasOwnProperty() or Object.hasOwn() to check that a key belongs directly to the object, not its prototype chain.
function Animal(name) {
this.name = name;
}
Animal.prototype.type = "animal"; // Inherited property
const dog = new Animal("Rex");
// Without guard β includes inherited key
for (const key in dog) {
console.log(key); // name, type
}
// With guard β own properties only
for (const key in dog) {
if (Object.hasOwn(dog, key)) {
console.log(key); // name only
}
}
for...of vs for...in β Comparison
| Feature | for...of | for...in |
|---|---|---|
| Iterates over | Values of an iterable | Enumerable keys of an object |
| Works on arrays | Yes (preferred) | Yes (but gives indices as strings) |
| Works on objects | No (objects aren't iterable by default) | Yes |
| Works on strings | Yes (characters) | Yes (indices as strings) |
| Includes inherited | No | Yes β use hasOwnProperty guard |
| ES version | ES6+ | ES1 (original) |
Nested For Loops
A loop inside another loop β common for processing 2D arrays or generating combinations.
// Multiplication table (3x3)
for (let i = 1; i <= 3; i++) {
let row = "";
for (let j = 1; j <= 3; j++) {
row += (i * j).toString().padStart(4);
}
console.log(row);
}
// Traverse a 2D array
const grid = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
for (const row of grid) {
for (const cell of row) {
process.stdout.write(cell + " ");
}
console.log();
}
Loop Variable Scope: let vs var
Using var in a for loop creates a function-scoped variable, which can cause bugs when the loop variable is captured in a closure.
// Bug with var β all closures share the same i
const funcsVar = [];
for (var i = 0; i < 3; i++) {
funcsVar.push(function() { return i; });
}
console.log(funcsVar[0]()); // 3 (not 0!)
console.log(funcsVar[1]()); // 3
console.log(funcsVar[2]()); // 3
// Fixed with let β each iteration gets its own i
const funcsLet = [];
for (let j = 0; j < 3; j++) {
funcsLet.push(function() { return j; });
}
console.log(funcsLet[0]()); // 0
console.log(funcsLet[1]()); // 1
console.log(funcsLet[2]()); // 2
When to Use forEach Instead
Array.prototype.forEach is a functional alternative when you just want to do something with each element and don't need break, continue, or await.
const nums = [1, 2, 3, 4, 5];
// forEach β clean, but cannot break out
nums.forEach((n, index) => {
console.log(index + ": " + n);
});
// Use for...of when you need to break
for (const n of nums) {
if (n === 3) break; // forEach can't do this
console.log(n);
}
ποΈ Practical Exercise
Given the array const students = [{name:"Alice",grade:88},{name:"Bob",grade:72},{name:"Carol",grade:95}]:
- Use a classic for loop to print each student's name and grade.
- Use for...of with destructuring to compute the average grade.
- Use for...in on the first student object to list all its keys.
π₯ Challenge Exercise
Write a function flattenMatrix(matrix) that uses nested for loops to convert a 2D array into a 1D array. Then write a second version using for...of with destructuring. Test with a 3Γ4 matrix of your choice.
π Summary
- Classic
for (init; cond; update)β precise control, index access. for...ofβ cleanest for iterating values of arrays, strings, Sets, Maps.for...inβ for object keys; useObject.hasOwn()to skip inherited keys.- Always use
let(notvar) for loop counters to avoid closure bugs. forEachis clean but can't usebreak,continue, orawait.
Interview Questions
- What is the difference between for...of and for...in?
- Why does using
varin a for loop cause closure bugs? How doesletfix it? - Can you use for...of on a plain object? Why or why not?
- What are the limitations of forEach compared to a for loop?
- How do you iterate over the entries (key-value pairs) of an object?
Frequently Asked Questions
Yes, NodeList is iterable since modern browsers implement the iterable protocol. You can write for (const el of document.querySelectorAll('p')) { ... }. For older browsers, convert first: Array.from(nodeList).
Use Array.entries(): for (const [index, value] of arr.entries()) { ... }. This gives you both the index and the value with clean destructuring syntax.
In micro-benchmarks, a classic for loop can be marginally faster. However, the difference is negligible for most applications. Prefer for...of for readability; switch to a classic loop only if you have measured a real performance bottleneck.