JAVASCRIPT / LOOPS
while and do-while loops with exit conditions
Write while and do-while loops that stop for the right reason: test-before versus test-after, and exit conditions the body actually moves.
What you will learn
- Choose while for zero-or-more passes and do-while when the first pass must always run
- Name the state the condition reads and the body line that pushes it toward false
- Join a data test with a hard cap using && to give one loop two independent exits
- Re-test the state after the loop to learn which exit condition ended it
Understanding while and do-while loops with exit conditions
A while loop is an if statement that repeats: JavaScript evaluates the condition, runs the body if it is truthy, then jumps back and evaluates the condition again. Because the first evaluation happens before any body code, a while loop can run zero times, which is what makes it the right shape for work whose size you cannot know up front - draining a queue, walking a chain of references, retrying until something succeeds. The condition is not a running description of the loop; it is a question asked at exactly one moment, the top of each pass, so making it false halfway through the body does not stop the pass already in progress.
The do { ... } while (cond); form moves that question to the bottom. The body's first run is unconditional, so the loop runs one or more times, and it can leave state on the wrong side of the line the condition was defending: starting from tank = 0 with a body that subtracts 1, the do-while exits with tank at -1 while the plain while never touches it. Reach for do-while only when the first pass is what produces the information the condition has to judge - emitting at least one digit, reading a first chunk, showing a prompt before checking the answer.
Whichever form you use, the loop ends only because the body changes something the condition reads. That is the entire exit-condition mental model: pick the state, initialise it before the loop, and be able to point at the line inside the body that moves it toward false. A loop can also have more than one exit - while (!found && tries < 5) stops on either one - and since JavaScript keeps no record of which side of the && failed, the code after the loop must re-test the state to know whether it succeeded or gave up.
let fuel = 3;
while (fuel > 0) {
fuel -= 1;
console.log("while burned 1, fuel now", fuel);
}
// Same body, same starting value, but the test happens at a different time.
let tank = 0;
while (tank > 0) {
tank -= 1;
console.log("while burned 1, tank now", tank);
}
console.log("while skipped entirely, tank still", tank);
do {
tank -= 1;
console.log("do-while burned 1, tank now", tank);
} while (tank > 0);A while loop asks its exit question at the top of each pass and a do-while asks it at the bottom, so the loop ends only once the body has changed the state that question reads.
Worked examples
do-while so zero still produces a digit
Converting a number to binary needs one unconditional pass, because 0 must still emit a character.
function toBinary(n) {
let digits = "";
do {
digits = (n % 2) + digits;
n = Math.floor(n / 2);
} while (n > 0);
return digits;
}
console.log(toBinary(13));
console.log(toBinary(0));Example explained
Line 1digits = (n % 2) + digits prepends the low bit, and the number is coerced to a string by the +.
Line 2The body runs before n > 0 is ever tested, which is why toBinary(0) returns "0" instead of an empty string.
Line 3n = Math.floor(n / 2) is the exit driver: it strictly shrinks n toward 0 for every positive input.
Line 4A while version of the same body would need a separate if (n === 0) return "0" special case.
A queue whose length changes during the loop
The condition is re-read each pass, so the body can add work and extend the loop it is running inside.
const children = { a: ["b", "c"], b: ["d"], c: [], d: [] };
const queue = ["a"];
const visited = [];
while (queue.length > 0) {
const node = queue.shift();
visited.push(node);
queue.push(...children[node]);
}
console.log(visited.join(" -> "));
console.log("passes:", visited.length);Example explained
Line 1queue.length > 0 is evaluated fresh at the top of every pass, so items pushed during a pass are seen.
Line 2queue.shift() removes the item being handled; that shrinking is what eventually makes the condition false.
Line 3The four passes are not knowable before the loop starts, since the body discovers new work as it goes.
Line 4children[node] returning [] is what stops the growth - a cycle in that map would keep the condition true forever.
Two exit conditions and telling them apart
A data test plus an attempt cap means the loop can stop for either reason, so the state is checked afterwards.
const readings = [null, null, null, 99];
let i = 0;
let value = null;
while (value === null && i < 3) {
value = readings[i];
console.log("attempt", i + 1, "->", value);
i += 1;
}
if (value === null) {
console.log("gave up after", i, "attempts");
} else {
console.log("got", value, "on attempt", i);
}Example explained
Line 1value === null && i < 3 gives the loop two independent exits: a usable reading, or the attempt cap.
Line 2i += 1 sits at the end of the body, so i counts completed attempts and reads 3 once the loop ends.
Line 3The cap wins here, so readings[3] holding 99 is never read - the loop stopped before reaching it.
Line 4The if after the loop is the only way to learn which exit fired, since the loop leaves no trace of that.
Important notes
A let or const declared inside a do block is out of scope in the while condition, because the block closes before the test runs: do { let chunk = next(); } while (chunk) throws a ReferenceError. Declare chunk above the loop.
Keep side effects out of the condition. while (item = queue.shift()) stops silently on a falsy item such as 0 or an empty string; test queue.length > 0 and shift inside the body instead.
Common mistakes
Updating a different variable than the condition reads, such as while (i < 10) with j += 1 in the body - the condition never changes and the tab locks up until you force it closed.
Using do-while on data that may be empty: do { total += prices[i]; i += 1; } while (i < prices.length) on [] reads undefined and turns total into NaN before the test ever runs.
Snapshotting the condition's input first, as in const left = queue.length; while (left > 0) queue.shift(); - left is a copy, so shift never affects it and the loop never ends.
Try it yourself
Change, predict, then run
In a browser console, write a do-while that logs a number and then replaces it with Math.floor(n / 10) until it reaches 0, and run it with 5203 and with 0. Change the do-while into a while with the same body and re-run both to see which starting value now logs nothing.
Open the JavaScript workspaceCheck your understanding
A loop is written while (!found && tries < 5) { ... }. Once it ends, what does the code that follows actually know?
- That found is true, because a while loop only exits when its search succeeds.
- That tries is exactly 5, because the condition keeps the loop running until the cap.
- Nothing on its own - it must re-test found or tries to tell success from running out of tries.
- That found is true and tries is 5, because && only stops the loop when both sides fail.
Show answer
The condition evaluated to false, but && becomes false as soon as either side does, so the loop may have stopped because a match was found or because the cap was hit, and JavaScript stores nothing about which. Option 4 is tempting because the two tests are joined by &&, but && needs only one false operand, so tries can still be 2 when the loop ends.