What should a loop know before it starts? The answer is more useful than memorizing that `for` is “for a known number of times” and `while` is “for an unknown number.” JavaScript loops differ in what they make visible: an index, a value, an object property, a guaranteed first run, or a condition that must eventually become false.
Imagine a program processing five notifications, retrying a network request, and inspecting the keys of a settings object. All three tasks repeat work, but they do not have the same control-flow contract. Choosing a loop means choosing which fact the code should communicate.
Round one: name the invariant
An invariant is something that should remain true—or become predictably closer to true—during every iteration. For a counter loop, the index should move toward the array length. For a retry loop, the attempt count should increase and the operation should eventually succeed or stop. For an object loop, the code should visit properties rather than pretend an object is an array.
| Task | Important invariant | Natural candidate |
|---|---|---|
| Visit each array value. | Move to the next iterable value. | for...of |
| Use an index to update positions. | Index remains within the intended range. | Classic for |
| Retry until success or a limit. | Attempts increase and a stop condition exists. | while |
| Ask at least once. | Body runs before the condition is checked. | do...while |
| Inspect object property names. | Each enumerable key is handled deliberately. | for...in |
MDN describes loops as repeated actions with different ways to determine their start and end points in its JavaScript guide. The table is not a set of laws. It is a way to ask what the code needs to expose.
Round two: the classic for loop shows the three controls
A classic `for` loop places initialization, condition, and update in one header:
const notifications = ["Welcome", "Update", "Reminder"];
for (let index = 0; index < notifications.length; index += 1) {
console.log(index, notifications[index]);
}The loop starts `index` at zero, checks the condition, runs the body, updates the index, and returns to the condition. It is a good choice when the index is part of the job: updating an array position, comparing neighboring items, or stopping at a calculated boundary.
The failure case is a missing or incorrect update:
for (let index = 0; index < notifications.length;) {
console.log(notifications[index]);
// index never changes: the loop does not move toward termination
}When a loop does not terminate, inspect the variable that controls the condition. Ask: what changes it, and can that change make the condition false?
Round three: for...of competes on readability
If the task is to use each value and the index is irrelevant, `for...of` expresses the intent directly:
for (const notification of notifications) {
console.log(notification);
}MDN explains that `for...of` iterates over values from an iterable, including arrays, maps, and sets. It avoids the repeated `notifications[index]` expression and reduces the number of moving parts the reader must track.
| Code shape | It communicates | Use when |
|---|---|---|
for (let i = 0; i < items.length; i++) | “I need the position and a manual boundary.” | Index-based updates or neighbor comparisons. |
for (const item of items) | “I need each value.” | Reading or transforming iterable values. |
for (const key in object) | “I need enumerable property names.” | Inspecting object keys with care. |
The shorter version is not automatically better. If the index is necessary, hiding it by searching for the value later can make the code less clear.
Round four: while carries a condition from the outside
A `while` loop checks its condition before each run. This is useful when the number of iterations depends on state that changes inside the loop:
let attempts = 0;
let connected = false;
while (!connected && attempts < 3) {
attempts += 1;
connected = tryConnection();
}
console.log({ attempts, connected });The loop has two termination paths: the connection succeeds, or three attempts have been used. The second condition is essential. A network call, user input, or external state must not be allowed to create an infinite loop by accident.
MDN describes `while` as testing its condition before executing the body and warns that the condition must eventually become false. Write the stop condition so a reviewer can see both success and exhaustion.
Round five: do...while guarantees one attempt
A `do...while` loop executes its body once before checking the condition. That is useful for a menu that must display at least once or input that must be requested before validation:
let choice;
do {
choice = prompt("Choose A, B, or Q to quit:");
} while (choice !== "A" && choice !== "B" && choice !== "Q");If the condition belongs before the first attempt, use `while`. If the user must see the prompt once, `do...while` makes that contract visible. Do not use it merely because it looks different.
Round six: for...in is about keys, not array values
Objects have enumerable properties. `for...in` iterates over property names:
const settings = {
theme: "dark",
language: "en"
};
for (const key in settings) {
console.log(key, settings[key]);
}Do not use `for...in` as a shortcut for arrays. MDN notes that it can include user-defined properties on an array in addition to numeric indexes, while `for...of` visits array values. The wrong loop can appear to work until the data structure gains an extra property or comes from a different object shape.
const colors = ["red", "blue"];
colors.label = "primary";
for (const item of colors) {
console.log(item); // red, blue
}
for (const key in colors) {
console.log(key); // 0, 1, label
}The code is not merely stylistic. It asks whether the domain contains values or properties.
Semifinal: break and continue change the path
`break` stops the nearest loop. `continue` skips the rest of the current iteration and starts the next one:
const usernames = ["ana", "", "carlos", "blocked"];
for (const username of usernames) {
if (username === "") {
continue;
}
if (username === "blocked") {
break;
}
console.log(username);
}These statements can make a loop concise, but they also create hidden exits. Use them when the early exit is part of the task, and make the condition easy to locate. In nested loops, consider whether a helper function would make the control flow easier to test.
The final: choose by the information the reader needs
| If the code needs to communicate... | Prefer... | Watch for... |
|---|---|---|
| Manual index and boundary. | Classic for | Off-by-one errors and missing updates. |
| Each value from an iterable. | for...of | Whether the object is actually iterable. |
| Property names from an object. | for...in or explicit keys | Inherited or unexpected enumerable properties. |
| Unknown count with a changing condition. | while | Infinite loops and unbounded retries. |
| At least one execution. | do...while | Whether the first attempt is safe and meaningful. |
For array transformations, a method such as `map`, `filter`, or `find` may express the purpose even more directly. That is not a reason to avoid loops; it is a reason to choose the construct that keeps the operation’s intention visible.
Our articles on JavaScript arrays, functions, and common JavaScript mistakes provide the surrounding concepts. A loop becomes easier to choose when you know whether the data is an array, iterable, object, or state machine.
Four short challenges
- Print every value in an array without using an index. Which loop communicates that intent?
- Ask a user for a valid choice at least once. Which loop guarantees the first prompt?
- Try a request up to three times. Which variable proves the loop is moving toward a stop?
- Print the keys of an object without accidentally treating it as an array. Which iteration target matters?
Write each solution, then change the input: use an empty array, an object with an extra property, a success on the first attempt, and a failure on every attempt. The edge cases reveal whether the loop’s contract was understood.
Questions beginners ask about loops
Is a for loop always better when I know the number of iterations?
Often it is a clear choice, but the better question is what the body needs to express. A `for...of` loop may be clearer when you need each value rather than a counter.
Why does my while loop run forever?
Find the condition and identify which value should change it. If no statement moves that value toward a false condition, the loop has no visible termination path.
Can I use for...in with arrays?
You can, but it iterates property names and may include custom enumerable properties. Use `for...of` for values or a classic `for` when you need indexes.
When should I use break?
Use it when finding a result or reaching a stop condition means the remaining iterations are irrelevant. If many breaks make the function hard to trace, extract smaller functions or clarify the state.
Make termination part of the design
A loop is a promise that repetition has a reason and an end. `for` exposes a counter, `for...of` exposes values, `for...in` exposes keys, `while` exposes a changing condition, and `do...while` exposes a guaranteed first attempt.
Choose the structure that tells the next reader what must remain true and what will eventually stop. The most readable loop is the one whose invariant and exit are easiest to verify.
Trace the next JavaScript failure with evidence →
Extra time: prove that the loop can finish
Before shipping a loop, test the smallest and most difficult inputs. An empty array should usually produce no output without an exception. A retry loop should stop after its limit. A `do...while` should not accept invalid input just because it ran once. Write those cases down instead of relying on a happy-path demo.
function collectLabels(items) {
const labels = [];
for (const item of items) {
if (!item.label) {
continue;
}
labels.push(item.label);
}
return labels;
}
console.log(collectLabels([]));
console.log(collectLabels([{ label: "A" }, {}, { label: "B" }]));The MDN reference for for...of emphasizes that the construct receives iterable values one at a time. Pair that with the broader loops guide, then ask whether `map` or `filter` would express a pure transformation more directly. A loop is not a badge of seriousness; it is one way to make repetition explicit.

Alex Carter is the editorial name behind Vandutz Academy, a programming blog for beginners. Alex reviews and tests the examples and explanations published on the site, with a focus on making Python, JavaScript, web development, and developer tools easier to understand.