The while loop is used when you do not know in advance how many iterations are needed โ you loop until a condition changes. The do...while loop is a variant that guarantees the body runs at least once before checking the condition. These loops are essential for event-driven patterns, retry logic, polling, and consuming streams of data. In this lesson you will master both, learn how to prevent infinite loops, and build practical patterns like retry mechanisms and queue processors.
while vs do…while vs for
| Loop | Condition Checked | Minimum Runs | Best For |
|---|---|---|---|
for |
Before each iteration | 0 | Known iteration count |
while |
Before each iteration | 0 | Unknown count โ condition may be false from the start |
do...while |
After each iteration | 1 โ always runs at least once | Must execute once before checking โ prompts, retries |
while Loop Anatomy
| Part | Location | Purpose |
|---|---|---|
| Condition | Before body | Evaluated before each iteration โ loop exits when false |
| Body | Inside { } |
Runs while condition is true |
| Update | Inside body (manual) | Must change something that eventually makes condition false |
| Safety counter | Inside body | Optional ceiling to prevent infinite loops during development |
Common while Patterns
| Pattern | Condition | Example Use Case |
|---|---|---|
| Drain a queue | queue.length > 0 |
Process all items until queue is empty |
| Retry with limit | attempts < MAX && !success |
API call retries with backoff |
| Read until sentinel | line !== 'EXIT' |
Input processing until stop signal |
| Converge to value | Math.abs(diff) > tolerance |
Numerical approximation algorithms |
| Validate input loop | !isValid |
do…while โ keep prompting until valid |
while and do...while: if the condition is false from the very beginning, a while loop runs zero times, but a do...while loop always runs at least once. Use do...while when the first execution is unconditional โ for example, showing a prompt before knowing if the input is valid.let safety = 0; while (condition && safety++ < 1000). If your condition variable has a bug and never becomes false, this cap prevents a browser freeze or server crash. Remove or increase it for production loops that legitimately need more iterations.while (isRunning) never sets isRunning = false, or while (i < 10) never increments i, the loop runs forever. Every while loop must have a clear path to making its condition false.Basic Example
// โโ Basic while loop โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
let countdown = 5;
while (countdown > 0) {
console.log(`T-minus ${countdown}...`);
countdown--; // update: will eventually make countdown > 0 false
}
console.log('Liftoff! ๐');
// โโ while to drain a queue โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const taskQueue = ['send-email', 'resize-image', 'generate-report', 'notify-user'];
console.log('
Processing queue:');
while (taskQueue.length > 0) {
const task = taskQueue.shift(); // remove first item
console.log(` Processing: ${task}`);
}
console.log('Queue empty.');
// โโ do...while โ always runs at least once โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Simulating user input validation (in a real app this would use a prompt)
const inputs = ['', ' ', 'Alice']; // simulate three attempts
let inputIndex = 0;
let validName;
do {
const raw = inputs[inputIndex++] ?? '';
validName = raw.trim();
if (!validName) console.log('Name cannot be empty โ try again.');
} while (!validName && inputIndex < inputs.length);
console.log(`
Valid name entered: "${validName}"`);
// โโ while with break โ search until found โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function findFirstEven(numbers) {
let i = 0;
while (i < numbers.length) {
if (numbers[i] % 2 === 0) {
return numbers[i]; // return acts like break + value
}
i++;
}
return null;
}
console.log(findFirstEven([7, 3, 11, 8, 5])); // 8
console.log(findFirstEven([1, 3, 5, 7])); // null
// โโ Retry pattern โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function simulateApiCall(attempt) {
// Simulates a flaky API that fails the first 2 times
if (attempt < 3) throw new Error(`Network error on attempt ${attempt}`);
return { data: 'Success!', attempt };
}
function fetchWithRetry(maxAttempts = 5) {
let attempt = 1;
let lastError = null;
while (attempt <= maxAttempts) {
try {
const result = simulateApiCall(attempt);
console.log(`
Succeeded on attempt ${attempt}:`, result.data);
return result;
} catch (err) {
lastError = err;
console.log(`Attempt ${attempt} failed: ${err.message}`);
attempt++;
}
}
throw new Error(`All ${maxAttempts} attempts failed. Last error: ${lastError.message}`);
}
fetchWithRetry();
// Attempt 1 failed: Network error on attempt 1
// Attempt 2 failed: Network error on attempt 2
// Succeeded on attempt 3: Success!
How It Works
Step 1 โ while Checks Before Every Iteration
Before the body runs each time, JavaScript evaluates the condition. If it is truthy, the body runs. If it is falsy, the loop exits. This means if countdown starts at 0, the body never runs at all โ the condition fails on the first check.
Step 2 โ do…while Checks After the First Run
In the validation example, the first prompt always happens โ then the condition !validName is checked. If the name is still empty, it loops again. This is the natural model for “ask at least once, then keep asking if needed.”
Step 3 โ Array.shift() as Loop Terminator
taskQueue.shift() removes and returns the first element. Each iteration shrinks the array by one. When the array is empty, taskQueue.length > 0 becomes false and the loop exits naturally. This draining pattern is common for processing job queues and event queues.
Step 4 โ try/catch Inside while for Retry Logic
Wrapping the operation in try/catch inside the while loop catches errors without exiting the loop. The attempt counter increments in the catch block. When the operation succeeds, return exits the function entirely. When all attempts fail, the code after the while loop throws with the last error.
Step 5 โ Always Have a Termination Path
Every while loop needs at least one of: a variable that changes towards making the condition false, a break inside the body, or a return inside the body. The retry example has all three: attempt++ moves toward maxAttempts, return result exits on success, and throwing after the loop handles total failure.
Real-World Example: Pagination and Rate Limiting
// paginated-fetch.js โ fetch all pages of a paginated API
async function fetchAllPages(baseUrl, maxPages = 20) {
const allItems = [];
let page = 1;
let hasMore = true;
while (hasMore && page <= maxPages) {
console.log(`Fetching page ${page}...`);
const response = await fetch(`${baseUrl}?page=${page}&per_page=20`);
if (!response.ok) {
throw new Error(`HTTP ${response.status} on page ${page}`);
}
const data = await response.json();
allItems.push(...data.items);
// API signals last page via a flag or empty next page
hasMore = data.hasNextPage ?? data.items.length === 20;
page++;
}
console.log(`Fetched ${allItems.length} total items across ${page - 1} pages`);
return allItems;
}
// Rate-limited queue processor
async function processWithRateLimit(items, processFn, requestsPerSecond = 5) {
const delayMs = 1000 / requestsPerSecond;
let i = 0;
while (i < items.length) {
const start = Date.now();
await processFn(items[i]);
i++;
// Throttle: wait for the remainder of the time slot
const elapsed = Date.now() - start;
const wait = Math.max(0, delayMs - elapsed);
if (wait > 0) await new Promise(r => setTimeout(r, wait));
}
}
Common Mistakes
Mistake 1 โ Infinite loop โ condition never becomes false
โ Wrong โ i is never incremented so condition stays true forever:
let i = 0;
while (i < 5) {
console.log(i);
// forgot i++ โ infinite loop, browser tab freezes
}
โ Correct โ always update the condition variable:
let i = 0;
while (i < 5) {
console.log(i);
i++; // moves toward termination
}
Mistake 2 โ Using while when for is clearer
โ Wrong โ while loop for a known count is harder to read:
let i = 0;
while (i < items.length) {
process(items[i]);
i++;
}
โ Correct โ for loop when count is known:
for (let i = 0; i < items.length; i++) {
process(items[i]);
}
// Or even cleaner:
for (const item of items) { process(item); }
Mistake 3 โ Skipping do…while when first run is unconditional
โ Awkward โ duplicated code to run once before the loop:
let input = prompt('Enter name:');
while (!input?.trim()) {
input = prompt('Enter name:'); // duplicated prompt call
}
โ Correct โ do…while expresses “ask at least once” naturally:
let input;
do {
input = prompt('Enter name:');
} while (!input?.trim());
Quick Reference
| Loop | Syntax | Minimum Executions |
|---|---|---|
| while | while (condition) { update; } |
0 โ condition checked first |
| do…while | do { update; } while (condition); |
1 โ always runs once |
| Drain queue | while (queue.length > 0) { queue.shift(); } |
0 if empty |
| Retry | while (attempt <= max) { try/catch; attempt++; } |
1 |
| Safety cap | while (cond && safety++ < 1000) |
0 โ dev guard |