JavaScript Conditionals and Loops
AI-assistedDrafted with AI assistance and reviewed before publishing; examples are version-pinned. Verify against official docs when in doubt.
The syntax of branching and looping is short. The bugs come from choosing the wrong form: wrong results while iterating, break that does not break, keys inherited from a prototype.
if / else and the conditional operator
const score = 85;
if (score >= 90) {
console.log("excellent");
} else if (score >= 60) {
console.log("pass");
} else {
console.log("fail");
}
When there are exactly two branches and both produce a value, the conditional operator is shorter:
const label = score >= 60 ? "pass" : "fail";
More than one level of nesting and you should go back to if. Readability wins over line count.
switch for equality matching
const status = "pending";
switch (status) {
case "pending":
console.log("waiting");
break;
case "done":
console.log("finished");
break;
default:
console.log("unknown");
}
Two rules:
switchcompares with===, so"1"does not match1.- Every case needs
break, or execution falls through to the next one. Intentional fall-through should carry a comment saying so.
For mapping a value to a result, an object lookup is often cleaner than switch:
const MESSAGES = { pending: "waiting", done: "finished" };
const message = MESSAGES[status] ?? "unknown";
for: when you need the index
const names = ["ann", "bob", "cindy"];
for (let i = 0; i < names.length; i++) {
console.log(i, names[i]);
}
Caching names.length in a variable no longer helps performance in modern engines; do not trade readability for it.
for…of: when you only need values
for (const name of names) {
console.log(name);
}
for...of iterates values and works on any iterable: arrays, strings, Set, Map, generators. It cannot go out of bounds and expresses intent better than an index loop.
If you need both, use entries():
for (const [i, name] of names.entries()) {
console.log(i, name);
}
for…in: not for arrays
for...in iterates enumerable property names, including inherited ones:
const arr = ["a", "b"];
arr.custom = "x";
for (const key in arr) {
console.log(key); // "0", "1", "custom"
}
Note that the keys are strings. Sorting is not guaranteed either. Iterate arrays with for...of or array methods.
For plain objects, for...in works but should be guarded:
const user = { name: "ann", age: 30 };
for (const key in user) {
if (Object.hasOwn(user, key)) {
console.log(key, user[key]);
}
}
In most cases Object.keys(user) or Object.entries(user) is clearer, because you get a real array and can use map / filter on it.
while and do…while
while fits loops whose iteration count is unknown up front:
let retry = 0;
while (retry < 3) {
console.log(`attempt ${retry + 1}`);
retry++;
}
do...while runs the body at least once, which suits “read input, then decide whether to repeat”.
A while loop without a state change hangs the process. Always identify what makes the condition false before writing the body.
break and continue
for (const n of [1, 2, 3, 4, 5]) {
if (n === 2) continue; // skip this iteration
if (n === 4) break; // exit the loop
console.log(n);
}
// 1 3
Neither works inside forEach:
[1, 2, 3].forEach((n) => {
if (n === 2) break; // SyntaxError: Illegal break statement
});
Inside forEach, return skips the current item (like continue). To stop early, use for...of with break, or a method with short-circuit semantics: some(), every(), find(), findIndex().
const hasNegative = nums.some((n) => n < 0); // stops at the first match
const firstEven = nums.find((n) => n % 2 === 0);
Labels for nested loops
outer:
for (const row of [[1, 2], [3, 4]]) {
for (const n of row) {
if (n === 3) break outer; // exits both loops
console.log(n);
}
}
// 1 2
Labels are rare, but when you need to leave two levels at once they beat a boolean flag.
A worked example
const orders = [
{ id: 1, amount: 120, paid: true },
{ id: 2, amount: 80, paid: false },
{ id: 3, amount: 200, paid: true },
];
let total = 0;
let firstUnpaid = null;
for (const order of orders) {
if (!order.paid) {
firstUnpaid ??= order; // record only the first one
continue;
}
total += order.amount;
}
console.log(total); // 320
console.log(firstUnpaid.id); // 2
The same logic with array methods:
const total = orders.filter((o) => o.paid).reduce((sum, o) => sum + o.amount, 0);
const firstUnpaid = orders.find((o) => !o.paid);
Prefer the second version when the operations map cleanly; keep the loop when you need both results in one pass or early exit.
Common mistakes
1. Missing break in switch
Two cases run and you get a result that looks half-applied.
2. for...in on an array
String indices, extra properties, no ordering guarantee.
3. break inside forEach
Syntax error. Use for...of, some, or find.
4. while without updating the condition
The script hangs; in a server it blocks every request on that worker.
5. Splicing while iterating
for (let i = 0; i < items.length; i++) {
if (items[i].stale) items.splice(i, 1); // skips the next element
}
Filter into a new array, or iterate backwards when mutating in place.
6. Off-by-one in index loops
i <= arr.length reads one element past the end. Use for...of and the problem disappears.
Summary
- Index loops for positions;
for...offor values;entries()for both. for...initerates keys, and never belongs on an array.switchuses===; writebreakor use an object lookup.forEachcannot be interrupted; usefor...ofor a short-circuiting method.- Never mutate the collection you are iterating.
Next: functions, scope chains, and closures.