Loops

Repeating an action multiple times without copy-pasting code.

What is it?

Sometimes you need to do the same thing many times — print every item in a list, check every user, count from 1 to 100. Writing that out by hand would be tedious and error-prone. A loop repeats a block of code automatically, either a fixed number of times or until a condition is no longer true.

The two you'll use most:

  • for — when you know how many times to repeat (or you're going through a list)
  • while — when you want to repeat until some condition becomes false

Explain like I'm 10

A loop is like giving someone instructions: 'Keep folding laundry until the basket is empty.' They don't need a new instruction for every item — just one instruction repeated.

Examples

A basic for loop

for (let i = 1; i <= 5; i++) {
  console.log(i);
}
// logs 1, 2, 3, 4, 5

i = 1 sets the start, i <= 5 is the condition checked before each run, and i++ increases i after each run.

A while loop

let count = 3;
while (count > 0) {
  console.log(count);
  count = count - 1;
}
// logs 3, 2, 1

A for...of loop — looping over an array directly

const fruits = ["apple", "banana", "cherry"];

for (const fruit of fruits) {
  console.log(fruit);
}
// logs apple, banana, cherry

for...of loops directly over the values in an array (or any other iterable), so there's no counter to manage and no risk of an off-by-one error.

How it works

Before each pass through the loop, JavaScript checks the condition. If it's true, it runs the loop's body, then checks again. This repeats until the condition becomes false, at which point the loop stops and the program continues after it.

Check condition
       ↓
  true? ──yes──▶ run loop body ──▶ back to check condition
    │
    no
    ↓
 exit loop

Why does it exist?

Loops let you write one small block of logic and apply it to many items or many repetitions, instead of duplicating code — which would be both tedious and hard to change later.

When to use it

Use a loop whenever you need to repeat the same action for every item in a collection, or repeat something an unknown number of times until a condition is met — processing a list, retrying a request, counting down a timer.

When not to use it

If you already know you only need to do something a fixed, small number of times, a loop can be overkill — a couple of plain repeated statements are sometimes clearer. And for common patterns like transforming every item in an array, methods like .map() and .filter() are usually more readable than a manual loop.

Common mistakes

  • Forgetting to update the loop variable, causing an infinite loop (e.g. forgetting i++).

  • Using <= vs < incorrectly and running one extra or one fewer time than intended (an 'off-by-one' error).

  • Modifying an array while looping over it, which can skip or repeat items.

Practice exercises

  1. Easy:

    Use a for loop to print the numbers 1 through 10.

  2. Medium:

    Use a while loop to add up all numbers from 1 to 100.

  3. Hard:

    Use a nested loop to print a 5x5 grid of * characters.

Interview questions

What's the difference between `for` and `while`?

A for loop bundles the counter's setup, condition, and update into one line, which makes it the natural fit when you know in advance how many times (or over what range) you need to repeat. A while loop only has a condition, checked before each pass, making it the natural fit when repetition depends on something that isn't a simple counter — no fixed number of iterations known ahead of time.

What causes an infinite loop?

A loop whose condition never becomes false — most often because the code that's supposed to change the value being tested (like i++ in a for loop, or updating a flag in a while loop) is missing, runs on the wrong branch, or accidentally moves the value in the wrong direction.

What do `break` and `continue` do?

break immediately exits the loop entirely — no further iterations run. continue skips only the rest of the current iteration's body and jumps straight to the next iteration's condition check, without exiting the loop.

What is a `for...of` loop, and what does it iterate over?

A loop that iterates directly over the values of any iterable — arrays, strings, Maps, Sets — without needing an index variable at all, so there's no counter to manage and no risk of an off-by-one mistake.

What's the difference between `for...of` and `for...in`?

for...of iterates over the values of an iterable (an array's elements, a string's characters). for...in iterates over the enumerable property keys of an object, given back as strings — using it on an array gives you the indices as strings ("0", "1", ...) rather than the values, and it's generally reserved for plain objects, not arrays.

What is a `do...while` loop, and how does it differ from a regular `while` loop?

It runs the loop body once before checking the condition for the first time, then continues checking before each subsequent pass, like a normal while. That guarantees at least one execution of the body even if the condition is false from the start, which a regular while loop never does.

What is an 'off-by-one' error, and how does it typically happen in a loop?

Running a loop one time too many or too few — usually from using <= when < was intended (or vice versa) in the condition, causing the loop to include or skip one extra element at a boundary, like looping i <= array.length and reading one index past the end of the array.

Why does modifying an array's length while looping over it with a classic indexed `for` loop cause bugs?

The loop's index keeps advancing based on the array's current length and contents at each check, but removing or adding elements shifts every subsequent element's index — so the loop can skip an element that shifted into an already-visited position, or process the same element twice, without ever throwing an error to flag it.

Inside a nested loop, does a plain `break` exit both loops or just the inner one?

Just the inner loop — a bare break only exits the nearest enclosing loop. To exit an outer loop from inside a nested one, you need a labeled statement, like outer: for (...) { for (...) { break outer; } } .

Can you use `continue` inside a `for...of` loop?

Yes — it works exactly like in any other loop, skipping the rest of the current iteration's body and moving on to the next value in the iterable.

Why is `for...of` generally preferred over a classic indexed `for` loop when you just need each value?

It removes an entire category of bugs tied to manually managing an index — off-by-one errors, wrong comparison operators, forgetting to increment — and it reads more directly as 'for each value in this collection,' since there's no counter variable cluttering the intent.

What's a key behavior difference between a classic `for` loop and `.forEach()`?

.forEach() calls a callback function once per element, which means it has its own function scope per call and works well with let/const closures naturally, but it also means break/continue/return don't affect the overall iteration the way they do in a real loop — you can't stop a .forEach() early.

Can `break` be used inside `.forEach()`? Why or why not?

No — break is a loop-control statement that only works inside actual loop constructs (for, while, do...while). .forEach() is a regular function that happens to be called repeatedly, not a loop syntactically, so break inside its callback is a SyntaxError. To stop early, you need a real loop, for...of with break, or a method like .some()/.find() that stops itself once satisfied.

Why does a `let` counter in a `for` loop create a new binding on every iteration, and why does that matter for closures?

The language spec specifically defines for (let i ...) to re-create and re-initialize a fresh i binding for each iteration (copying forward the previous value), rather than reusing one shared binding for the whole loop. This is exactly what lets a closure created inside the loop body — like a setTimeout callback — capture that iteration's own separate value of i, instead of every callback sharing one final value the way they would with var.

Output-prediction: what does `for (let i = 0; i < 3; i++) { if (i === 1) continue; console.log(i); }` log?

It logs 0 then 2. When i is 1, continue skips the rest of that iteration's body — the console.log never runs for i = 1 — and the loop moves straight on to i = 2.

Output-prediction: what does `outer: for (let i = 0; i < 2; i++) { for (let j = 0; j < 2; j++) { if (j === 1) continue outer; console.log(i, j); } } ` log?

It logs 0 0 then 1 0. continue outer skips straight to the next iteration of the labeled outer loop as soon as j is 1, before the inner loop's console.log for j = 1 ever runs — so each outer pass only ever gets to log its j = 0 case.

Trap: what's wrong with `let i = 0; while (i < 5) { console.log(i); }`, and what actually happens when it runs?

It's an infinite loop that logs 0 forever — nothing inside the loop body ever changes i, so the condition i < 5 stays true on every check. It's missing something like i++; inside the block to make progress toward the loop eventually ending.

Output-prediction: what does `const arr = ["a", "b", "c"]; for (const index in arr) { console.log(typeof index); }` log?

It logs "string" three times. for...in enumerates an array's property keys, and array indices — even though you'd normally think of them as numbers — are handed over as strings ("0", "1", "2"), which is one reason for...in is discouraged for arrays in favor of for...of or indexed access.