while and do-while
while is the simplest loop in JavaScript: while this condition is true, keep going. There's no counter to set up and no increment to remember — just a question. Use it when you don't know in advance how many iterations you need, like reading from a stream, retrying a request, or walking a tree until you hit the root.
The while loop
The condition is checked before each iteration. If it is falsy at the start, the body never runs.
let n = 1;
while (n < 100) {
n *= 2;
}
console.log(n); // 128 — first power of two >= 100128
Compare to a classic for loop: same job, but while doesn't pretend there's a counter when really we just care about a condition.
Reading until exhausted
Whenever you have a producer that yields data and signals "no more" somehow, while reads beautifully.
function consumeQueue(queue) {
while (queue.length > 0) {
const job = queue.shift();
handle(job);
}
}Walking up a DOM tree
let node = startNode;
while (node && node !== document.body) {
if (node.classList.contains("highlight")) break;
node = node.parentElement;
}do-while: run first, ask later
do { ... } while (cond) flips the order: the body runs first, then the condition is checked. The body is guaranteed to run at least once.
let answer;
do {
answer = prompt("Type 'yes' to continue");
} while (answer !== "yes");The classic use case is prompt-then-validate: ask the user a question, and keep asking until the answer is acceptable. With a plain while you'd need a flag or a redundant first read.
Choosing between them
The difference is just when the check happens:
Use
whilewhen there is a chance the loop should not run at all. Reading from an empty queue, polling for a flag that might already be set.Use
do...whilewhen you need at least one pass — running a step, then deciding whether to repeat.Use
forwhen there is an explicit counter or a fixed range.
Same logic, different shape
// while — 0 iterations if items is already empty
while (items.length > 0) {
process(items.pop());
}
// do-while — always at least 1 iteration, even if items is empty
do {
process(items.pop());
} while (items.length > 0);Infinite loops and clean breaks
while (true) is a deliberate infinite loop. You always pair it with at least one break (or return, or throw) inside the body.
Retry with backoff
async function fetchWithRetry(url, maxAttempts = 5) {
let attempt = 0;
while (true) {
try {
return await fetch(url);
} catch (err) {
attempt++;
if (attempt >= maxAttempts) throw err;
await sleep(2 ** attempt * 100); // 200ms, 400ms, 800ms, ...
}
}
}Two things make this readable: the only way out is success or running out of attempts, and the structure mirrors the English description ("try, if it fails wait and try again").
Accidental infinite loops
The classic mistake
let i = 0;
while (i < 10) {
console.log(i);
// forgot to increment i — runs forever
}Defences:
Always update the variable that controls the condition inside the loop.
When the condition involves an external source (a queue, a stream), make sure that source actually drains.
Add a sanity-check counter that throws after, say, 100k iterations during development.
Belt and braces
let guard = 0;
while (notDoneYet()) {
step();
if (++guard > 100_000) throw new Error("loop limit exceeded");
}break and continue
Both work exactly as in for. break ends the loop; continue jumps to the next condition check.
while (input = readLine()) {
if (input.startsWith("#")) continue; // skip comments
if (input === "END") break; // stop on sentinel
process(input);
}while vs recursion
Many problems can be expressed either as a loop or as a recursive function. When the recursion depth is unbounded, prefer while — JavaScript engines don't reliably eliminate tail calls, so deep recursion blows the stack.
Tree traversal — iterative form
function findFirst(root, predicate) {
const stack = [root];
while (stack.length > 0) {
const node = stack.pop();
if (predicate(node)) return node;
if (node.children) stack.push(...node.children);
}
return null;
}