Key Takeaways
- Use
for...offor iterable values andfor...infor enumerable string property keys, including inherited keys. - A
forloop groups initialization, a condition, and an update. Awhileloop checks its condition first;do...whileruns its body at least once. - For an object's own enumerable keys, use
Object.keys()or guard afor...inloop withObject.hasOwn(). - Use
breakto exit a loop andcontinueto skip the rest of an iteration. Neither controls an enclosing loop from inside aforEach()callback. - Use
forEach()for synchronous callback-style side effects andmap()to produce a transformed array.forEach()doesn't wait for async callbacks.
JavaScript loops repeat work while a condition holds or while a collection supplies more items. Start with for...of when you need array values, a counted for when you need index control, and for...in when you need enumerable object keys.
The important distinction is what you want to visit: values, property names, or a sequence controlled by a condition. These constructs complement the branching statements covered in JavaScript ES6 Control Flow.
Choosing Among JavaScript Loops
Choose a construct by its behavior, not by whether someone calls it a “definite” or “indefinite” loop. A for loop doesn't guarantee a known iteration count, and an iterable doesn't have to contain a fixed number of values.
| Your intent | Choose | Why |
|---|---|---|
| Control a counter or index | for | Initialization, condition, and update stay together. |
| Repeat while a condition holds | while | Checks the condition before each iteration. |
| Run once, then decide whether to repeat | do...while | Checks the condition after the body. |
| Visit iterable values | for...of | Supports ordinary loop control. |
| Visit enumerable property keys | for...in | Includes inherited enumerable string keys. |
| Perform synchronous callback-style side effects | forEach() | Calls a function for each present array element. |
| Create a transformed array | map() | Builds an array from callback results. |
The MDN loops and iteration guide documents the loop statements; array methods provide a separate callback-based approach.
Run each code example separately in a modern browser console or JavaScript runtime.
for, while, and do...while Loops
A JavaScript for Loop Groups the Moving Parts
for (let i = 0; i < 5; i++) {
console.log(i);
}
// logs 0, 1, 2, 3, 4The initializer runs once. Before each iteration, JavaScript checks i < 5; after the body, i++ advances the counter. This example runs five times, but a different condition could depend on changing runtime state or never become false.
A while Loop Checks Before Running
let x = 0;
while (x < 5) {
console.log(x);
x++;
}
// logs 0, 1, 2, 3, 4A while loop runs its body only while its condition is truthy. If the condition is false on the first check, the body doesn't run. Make sure something can change the condition or exit the loop.
A do...while Loop Runs Before Checking
let i = 0;
do {
console.log(i);
i++;
} while (i < 5);
// logs 0, 1, 2, 3, 4The condition comes after the body, so do...while always enters its body at least once. If this example started with i = 5, it would still log 5 once. Keep the trailing semicolon after the condition.
for...of vs for...in: Values Versus Keys
Use for...of for Array Values
const array = [1, 2, 3, 4];
for (const x of array) {
console.log(x);
}
// logs 1, 2, 3, 4With the normal array iterator, for...of yields values in index order. The variable receives the element, not its property name. A const declaration works because each iteration gets a new binding; see JavaScript let, const, and var for the underlying variable rules.
Now add a property that isn't an array index:
const colors = ["red", "blue"];
colors.label = "palette";
for (const value of colors) {
console.log(value);
}
// red
// blue
for (const key in colors) {
console.log(key, typeof key);
}
// 0 string
// 1 string
// label stringThe array iterator doesn't enumerate the added label property. The for...in loop does, and even the index keys are strings. This semantic difference—not a blanket performance claim—is why for...of is the better default for array values.
Don't replace the old ordering myth with a new one: modern for...in enumeration has defined ordering rules. It's still property enumeration, not array-value iteration.
Use for...in for Enumerable Object Keys
const obj = { a: 1, b: 2 };
for (const key in obj) {
console.log(key);
}
// logs 'a', 'b'An enumerable property is one whose property settings allow it to participate in enumeration. for...in visits enumerable string-keyed properties, including inherited ones; it ignores symbol-keyed properties.
An own property belongs directly to the object rather than coming from its prototype, the object it inherits from. When you only want own properties, add a guard:
const defaults = { inherited: "default" };
const settings = Object.create(defaults);
settings.own = "custom";
for (const key in settings) {
console.log(key);
}
// own
// inherited
for (const key in settings) {
if (Object.hasOwn(settings, key)) {
console.log(key, settings[key]);
}
}
// own customObject.hasOwn(object, key) checks ownership without depending on an object's own hasOwnProperty method. It also works with objects whose prototype is null.
For ordinary own-key traversal, Object.keys() is often simpler: it returns an array of own enumerable string keys. When you need both keys and values, Object.entries() returns their pairs:
const obj = { a: 1, b: 2 };
for (const key of Object.keys(obj)) {
console.log(key);
}
// a
// b
for (const [key, value] of Object.entries(obj)) {
console.log(key, value);
}
// a 1
// b 2Iterable Values Aren't Just Arrays
An iterable supplies values through the iterable protocol: a standard agreement between a value and the code consuming it. Its [Symbol.iterator]() method returns an iterator, whose next() method supplies results containing a value and a completion flag named done.
for...of handles that machinery for you. Strings, maps, and sets are iterable too:
for (const letter of "cat") {
console.log(letter);
}
// c, a, t
for (const number of new Set([1, 1, 2])) {
console.log(number);
}
// 1, 2
const scores = new Map([["Ada", 10], ["Lin", 20]]);
for (const [name, score] of scores) {
console.log(name, score);
}
// Ada 10
// Lin 20A map's default iterator supplies [key, value] pairs; the bracketed declaration unpacks each pair. The iteration protocols reference explains how custom iterables participate.
Array-Like Doesn't Mean Iterable
An array-like object can have indexed properties and a length without implementing iteration. Passing the following object directly to for...of would throw a TypeError; Array.from() can convert it to an array first:
const arrayLike = { 0: "a", 1: "b", length: 2 };
console.log(typeof arrayLike[Symbol.iterator]); // undefined
for (const value of Array.from(arrayLike)) {
console.log(value);
}
// a
// bA plain object such as { a: 1 } isn't iterable by default either. Use Object.keys() or Object.entries() when your intent is to traverse its properties.
Controlling Loops with break and continue
Use break to Exit Early
let array = [1, 2, 3];
for (let x of array) {
if (x === 2) {
break;
}
console.log(x);
}
// logs 1An unlabeled break exits the innermost enclosing loop or switch. Here, execution leaves the loop before logging 2.
Use continue to Skip the Rest of an Iteration
let array = [1, 2, 3];
for (let x of array) {
if (x === 2) {
continue;
}
console.log(x);
}
// logs 1, 3An unlabeled continue skips the rest of the current iteration of the innermost enclosing loop. In a counted for, it proceeds to the update expression; in while and do...while, it proceeds to the condition.
Nested Loops and Labels
For nested loops, break outer; can exit an enclosing statement labeled outer:, while continue outer; can advance an enclosing loop with that label. Labels don't let either statement jump across a function boundary. Prefer simpler control flow unless the label makes the nested algorithm clearer.
forEach vs for...of: Understand the Callback Boundary
forEach Calls a Function; It Isn't a Loop Statement
Array.prototype.forEach() calls your callback for each present array element in ascending index order:
const array = [1, 2, 3, 4];
array.forEach((x) => {
console.log(x);
});
// logs 1, 2, 3, 4A callback is a function passed to another function to be called later in its execution. Inside that callback, return ends only that function call—not the overall traversal:
const array = [1, 2, 3];
array.forEach((x) => {
if (x === 2) {
return;
}
console.log(x);
});
// logs 1, 3You can't use break or continue to control forEach() itself. Its callback return values are discarded, and the method returns undefined. Choose for...of when you need early exit or explicit iteration control.
Sparse Arrays Behave Differently
A sparse array contains missing elements, called holes. forEach() skips holes; normal array for...of iteration visits those positions and yields undefined when no indexed property exists there.
const sparse = [1, , 3];
sparse.forEach((value) => console.log(value));
// 1, 3
for (const value of sparse) {
console.log(value);
}
// 1, undefined, 3An explicit undefined element isn't a hole: forEach() visits it. Don't assume switching iteration styles preserves sparse-array behavior.
Use map When You Want a New Array
const numbers = [1, 2, 3, 4];
const doubled = numbers.map((number) => number * 2);
console.log(doubled); // [2, 4, 6, 8]map() creates a new array from callback results. Use it for transformation rather than calling it only for side effects and discarding its result. For more collection operations, see JavaScript array methods.
forEach Doesn't Await Async Callbacks
Writing await array.forEach(async (value) => { ... }) doesn't wait for the callback promises: you're awaiting the method's undefined return value. For sequential asynchronous work, put await inside a for...of loop instead.
async function run() {
const numbers = [1, 2, 3, 4];
const doubled = [];
for (const number of numbers) {
// A resolved promise stands in for an asynchronous operation.
const result = await Promise.resolve(number * 2);
doubled.push(result);
}
console.log(doubled); // [2, 4, 6, 8]
}
run().catch((error) => {
console.error("Processing failed:", error);
});Each iteration waits at await before moving to the next number. The loop doesn't automatically await anything—you explicitly request the wait in its body. See the JavaScript async/await example for more on promise-based control flow.
Common Mistakes
- Choosing by an assumed speed ranking. Choose the correct semantics first. Measure your actual workload before changing a loop for performance.
- Forgetting to advance a condition. In a
whileloop, check that acontinuecan't bypass the update that allows termination. - Treating for...in keys as array values. Its loop variable is a string property name, not the element stored at that property.
- Changing a collection without planning for it. Avoid adding or removing elements during traversal unless you've deliberately designed and tested that behavior.
FAQ
What is the difference between for...of and for...in in JavaScript?
for...of consumes values supplied by an iterable. for...in visits enumerable string property keys, including inherited keys, so it isn't the usual choice for traversing array values.
How do you loop through an array in JavaScript?
Use for...of to read array values with support for break and continue. Use a counted for loop when you need explicit index control, or map() when you want a transformed array.
Can you use break or continue in forEach()?
You can't use break or continue to control forEach() from its callback. A return statement exits only the current callback invocation; use for...of when you need loop-level control.
Does for...of work with plain objects?
Plain objects aren't iterable by default. Use for...of with Object.keys(object) for own enumerable string keys or Object.entries(object) for own enumerable string-keyed key-value pairs.
Does forEach() work with async functions?
forEach() can call an async function, but it doesn't wait for the returned promises. Awaiting forEach() doesn't await its callbacks; use for...of with await in the body for sequential asynchronous work.
Further Reading
- MDN: Loops and iteration — loop statements, labels, break, and continue.
- MDN: for...of — iterable values and built-in collection examples.
- MDN: for...in — enumerable keys, inheritance, and ordering.
- MDN: Iteration protocols — how iterables and iterators cooperate.
- MDN: Object.hasOwn() — checking property ownership safely.
- MDN: Array.prototype.forEach() — callback behavior, sparse arrays, and async limitations.
- MDN: Array.prototype.map() — creating transformed arrays.
- MDN: await — waiting for promises and resuming execution.
