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);
}
for, for...of, for...in: Pick the Right One
JavaScript has three for variants and they are not interchangeable β using the wrong one on an array is a classic source of bugs.
| Loop | Iterates | Use on |
|---|---|---|
for (let iβ¦) | an index counter | when you need the index |
for...of | values | arrays, strings, Maps, Sets |
for...in | keys (property names) | plain objects only |
const arr = ["a", "b", "c"];
for (const v of arr) console.log(v); // a, b, c β values (what you want)
for (const i in arr) console.log(i); // "0","1","2" β indices as STRINGS
The for...in trap: it iterates property names, not values, gives them as strings, includes inherited enumerable properties, and doesn't guarantee order β so it's wrong for arrays. Use for...of for array values, or a plain counter when you need the index.
forEach vs for...of: arr.forEach() is clean but you can't break or return out of it early, and it skips await. When you need to stop early or use await in the loop body, use for...of.
ποΈ 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?
Related Topics
FAQ
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.

