Skip to content

Loops

docs.scrimba.com

Say you want to print the numbers 1 to 100, or greet every character in a word, or keep asking a question until the answer is right. You could write the same line a hundred times, but that is tedious to type and worse to change. A loop is how you tell JavaScript to run a piece of code again and again, so you write the work once and let the language do the repeating. This chapter covers the three loops you reach for most: for, while, and for...of.

The for loop

The for loop is the one you use when you know how many times to repeat. It counts for you. Here it is printing the numbers 0 to 4:

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

The part in the parentheses has three pieces, separated by semicolons:

  • let i = 0 is the start: it creates a counter named i and sets it to 0.
  • i < 5 is the condition: the loop keeps going as long as this is true.
  • i++ is the step: after each pass, it adds 1 to i (i++ is short for i = i + 1).

So the loop starts i at 0, checks that i < 5, runs the code in the braces, then adds 1 and checks again. When i reaches 5 the condition is false and the loop stops.

The for loop is built for the case where you know the number of repeats up front. It packs three pieces into its header, run in order:

js
for (let i = 0; i < 5; i++) {
  console.log(`pass number ${i}`);
}
// pass number 0
// pass number 1
// pass number 2
// pass number 3
// pass number 4

Reading the header left to right: let i = 0 is the initialiser, run once before anything else. i < 5 is the condition, checked before every pass, and the loop runs only while it is true. i++ is the step, run after each pass. The counter i is a normal variable, so you can read it inside the loop, which is why loops and counting go together.

The counter starts at 0 by convention, not by rule. Counting from 0 is what you will see almost everywhere in JavaScript, and it lines up with how arrays number their items, so it is worth getting comfortable with early.

The for loop encodes the whole shape of a counted repeat in one header: initialiser, condition, and step, each separated by a semicolon.

js
for (let i = 0; i < 5; i++) {
  console.log(`pass number ${i}`);
}
// pass number 0
// pass number 1
// pass number 2
// pass number 3
// pass number 4

The order is exact and worth internalising: the initialiser runs once, then the condition is tested, and only if it is true does the body run, followed by the step, then back to the condition. That ordering is why the condition is checked before the first pass, so a loop whose condition starts false never runs its body at all.

Use let for the counter, not const, because the step reassigns it every pass. And declaring i in the header scopes it to the loop, so it does not leak into the surrounding code, which keeps counters from colliding when you nest loops or write several in a row. Counting from 0 rather than 1 is the near-universal convention here, and it maps directly onto how arrays index their elements, so the habit pays off the moment you loop over data.

JunoThe for loop The for loop repeats a fixed number of times and counts for you. Its header has three parts: a start like let i = 0, a condition like i < 5 that keeps it going, and a step like i++ that moves it along. When the condition turns false, the loop stops.
JunoThe for loop Reach for for when you know the count up front. The header runs the initialiser once, checks the condition before every pass, and runs the step after each one. Counting from 0 is the convention, and it lines up with how arrays number their items, so lean into it now.
JunoThe for loop The header order is fixed: initialise once, test the condition, run the body, run the step, repeat. That is why a condition starting false skips the body entirely. Use let for the counter so the step can reassign it, and declare it in the header so it stays scoped to the loop.

The while loop

Sometimes you do not know how many times to repeat, you only know a condition that should stay true. That is what the while loop is for. It keeps running as long as its condition holds:

js
let count = 3;
while (count > 0) {
  console.log(count);
  count = count - 1;
}
console.log("Go");
// 3
// 2
// 1
// Go

The loop checks count > 0, runs the code, then checks again. Notice that something inside the loop changes count, so the condition eventually becomes false and the loop stops. If nothing inside ever makes the condition false, the loop runs forever. That is an infinite loop, and it will freeze your program, so always make sure the condition can change.

A while loop repeats as long as a condition stays true, and it does not carry a built-in counter or step. That makes it the right tool when the number of repeats is not known ahead of time, and a for loop the right tool when it is.

js
let attempts = 0;
let password = "";
while (password !== "letmein") {
  password = "letmein"; // stand-in for a real prompt
  attempts = attempts + 1;
}
console.log(`Unlocked after ${attempts} attempt(s)`);
// Unlocked after 1 attempt(s)

The condition (password !== "letmein") leans on the comparison operators from conditionals, and the loop runs until it turns false. The catch is that nothing forces it to turn false: you have to change something inside the loop that the condition depends on. A while loop whose condition never becomes false runs forever. That is an infinite loop, and it hangs the program.

A while loop is the general form: it repeats while a condition is true and leaves the counting, if there is any, entirely to you. Reach for it when the number of iterations is not fixed, for example repeating until input validates or a value crosses a threshold. When the count is known, a for loop states that intent more clearly.

js
let remaining = 100;
let steps = 0;
while (remaining > 0) {
  remaining = remaining - 15;
  steps = steps + 1;
}
console.log(`Emptied in ${steps} steps`);
// Emptied in 7 steps

The failure mode to respect is the infinite loop: while gives you no structural guarantee of termination, so the body must move the condition toward false. Three things cause a loop to hang: a condition that is never updated, an update that moves the wrong direction, and a condition that can be stepped over rather than met (using remaining === 0 above would miss, since remaining lands on a negative number, not exactly 0). Every while loop needs a line that pushes its condition toward false. Prefer a range test like > 0 over an exact === 0 so a step that overshoots still ends the loop.

JunoThe while loop A while loop repeats as long as its condition is true, with no built-in counter. Use it when you do not know the number of repeats ahead of time. Always change something inside the loop so the condition can turn false, or you get an infinite loop that freezes the program.
JunoThe while loop Pick while when the number of repeats is unknown, and for when it is fixed. The body has to change whatever the condition tests, or it never turns false. That is the infinite loop, and it is the mistake to watch for every time you write a while.
JunoThe while loopwhile gives no guarantee it ever stops, so the body must push the condition toward false. Three ways it hangs: the condition is never updated, it moves the wrong way, or it gets stepped over. Prefer a range test like > 0 over an exact === 0 so an overshooting step still ends the loop.

Looping over a string with for...of

The for and while loops count with a number, but often you want to visit each item in something directly. for...of does that. Here it walks through each character of a word:

js
for (const char of "hello") {
  console.log(char);
}
// h
// e
// l
// l
// o

Read it as "for each character of the word". On each pass, char holds the next character, and you never touch a counter or a condition. It is cleaner than a for loop when all you want is to look at every item in turn.

You will mostly use for...of on arrays, which are lists of values you meet in the next chapters. A string is a good first thing to loop over because it is already a sequence of characters, and for...of treats it exactly like that.

for...of iterates the items of a sequence directly, handing you each value in turn without a counter or a condition to manage:

js
let vowels = 0;
for (const char of "javascript") {
  if (char === "a" || char === "i") {
    vowels = vowels + 1;
  }
}
console.log(`Found ${vowels} of those vowels`);
// Found 3 of those vowels

Using const char here is deliberate: each pass creates a fresh char that you only read, never reassign, so const fits and signals that intent. A for...of loop trades the manual bookkeeping of a counter loop for readability, and it removes a whole class of off-by-one mistakes because you never index by hand.

Strings are a natural first sequence to loop over, but the real workhorse is the array. Once you meet arrays, for...of is the clean, direct way to walk their contents.

for...of iterates any sequence element by element, binding each value to a name without exposing an index. On a string it yields characters:

js
let shout = "";
for (const char of "loops") {
  shout = shout + char.toUpperCase();
}
console.log(shout);
// LOOPS

Bind the loop variable with const: for...of creates a new binding each pass, so nothing needs to reassign it, and const documents that the value is read-only within the body. The payoff over a counter loop is that you never write an index, which sidesteps off-by-one errors by construction.

Where this matters most is arrays. For visiting each element, for...of reads cleaner than a counting for loop and states the intent directly. But when you are transforming a collection into a new one, the array iteration methods usually replace the manual loop entirely, and they are covered in their own chapter.

break and continue

Two keywords give you finer control inside any loop. break stops the loop immediately and moves on to the code after it. continue skips the rest of the current pass and jumps straight to the next one.

js
for (let i = 0; i < 10; i++) {
  if (i === 3) {
    continue; // skip 3, keep looping
  }
  if (i === 6) {
    break; // stop the loop entirely
  }
  console.log(i);
}
// 0
// 1
// 2
// 4
// 5

break is how you stop early once you have found what you need, so you do not keep scanning pointlessly. continue is how you skip cases you do not care about without nesting the rest of the body inside an if.

Off-by-one errors and index bounds

The classic loop bug is the off-by-one error: running one time too many or one too few. It comes from the counter's bounds. A string of length 5 has characters at positions 0, 1, 2, 3, and 4, so the last valid position is length - 1, not length. That is why a counting loop over its positions uses i < text.length, not i <= text.length:

js
const text = "hello";
for (let i = 0; i < text.length; i++) {
  console.log(text[i]);
}
// h
// e
// l
// l
// o

Write i <= text.length and the last pass reaches position 5, which does not exist, so it produces undefined. Counting from 0 with < up to the length is the pattern that lands exactly on every position and no more. When you only need each item and not its position, for...of avoids the bounds question completely, which is a reason to prefer it.

JunoLooping over a string with for...offor...of visits each item directly, no counter and no condition to manage. On a string, each pass gives you the next character. You will use it most on arrays later, but a string is a clean first thing to loop over because it is already a sequence of characters.
JunoLooping over a string with for...offor...of hands you each value in turn, so you skip the counter and the off-by-one risk that comes with indexing by hand. Bind with const since each pass is read-only. Strings are a fine start, but arrays are where you will lean on it once you meet them.
JunoLooping over a string with for...offor...of binds each element to a fresh const and never exposes an index, which removes off-by-one bugs by construction. break stops early, continue skips a pass. For transforming a collection rather than only walking it, the array iteration methods usually replace the manual loop, and they get their own chapter.

Where loops go from here

Loops are how you stop repeating yourself and let the language do the repeating. The for loop counts a known number of times, while repeats as long as a condition holds, and for...of walks the items of a sequence directly. The one habit to carry out of here: make sure every loop can end, and prefer for...of when you only need each item.

The real reason loops matter is that most of the data you work with comes in collections. The next chapters introduce arrays, the lists you will loop over constantly, and later the array methods that often replace a hand-written loop when you are transforming data. If you want to sharpen the conditions your loops depend on, conditionals covers the comparisons that decide when a loop keeps going and when it stops.