Mert Özen Further With Every Line
Frontend JavaScript Basics — #07

JavaScript Loops: for, while, for...of and for...in

Mert Özen Nov 3, 2026 8 min 6 views
JavaScript Loops: for, while, for...of and for...in

We use examples to cover for, while and do...while loops, for...of for arrays and strings, for...in for objects, break and continue, and the infinite loop trap that locks up the tab.

Version note: The code in this post targets ES2026 and current browsers (Chrome, Edge, Firefox, Safari).

Predict First

We have an array of three letters, and we want to print each element's position plus one:

const letters = ["a", "b", "c"];
for (const i in letters) {
  console.log(i + 1);
}

What does this loop print to the console? 1, 2, 3? Make a guess before you keep reading. We'll give the answer when we talk about what for...in actually loops over.

for: The Counting Loop

To do something five times, you could write the same line five times. But five hundred times? Loops let you run a block of code again and again for as long as a condition holds. The classic one is for. Paste this into your browser's console and try it:

for (let i = 1; i <= 5; i++) {
  console.log("Lap " + i);
}

You'll see five lines in the console, from "Lap 1" to "Lap 5." Inside the parentheses there are three parts separated by semicolons. let i = 1 is the initialization, and it runs only once, at the very start. i <= 5 is the condition; it's checked before every lap, and the loop ends the moment it's false. i++ is the step that runs at the end of every lap.

Think of a lap counter on a running track. At the start, the counter is reset. At the beginning of every lap, the referee checks "are there laps left to run?"; if so, the runner does a lap and the counter goes up by one. The moment the answer is "no," the race is over. The engine does exactly this: initialize, check, run, step, check again.

All three parts are entirely up to you. You can count backwards, or move in steps of three:

for (let i = 10; i > 0; i -= 3) {
  console.log(i);
}

The output is 10, 7, 4, 1. On the next step i becomes -2, the condition fails and the loop ends.

The most common use of for is walking through the elements of an array in order. In arrays, the first element is at position 0 and length gives the number of elements; we'll cover both in detail in the post on arrays:

const cities = ["Ankara", "Izmir", "Bursa"];
for (let i = 0; i < cities.length; i++) {
  console.log("City " + i + ": " + cities[i]);
}

Notice that the condition is i < cities.length. In a three-element array, the last index is 2. If you wrote <=, the loop would run one lap too many and print undefined on the last lap.

while: As Long as the Condition Holds

for is convenient when you know up front how many laps you'll run. Sometimes, though, you don't know the number of laps; you only know when to stop. Then while reads more naturally:

let value = 1;
let steps = 0;
while (value <= 1000) {
  value *= 2;
  steps++;
}
console.log(value, steps);

We said "keep doubling until the value passes 1000." The output is 1024 10: we passed 1000 in ten steps. while checks the condition before every lap; if the condition is false from the start, the block doesn't run even once. Preparing the variables before the loop and doing something inside the loop that changes the condition is your job. Forget that, and you'll see where you end up at the end of this post.

do...while: At Least Once

do...while is while turned upside down: it runs the block first and checks the condition afterwards. That's why the block always runs at least once. To see the difference, let's put both side by side with the same condition:

let a = 10;
do {
  console.log("do...while ran:", a);
  a++;
} while (a < 5);

let b = 10;
while (b < 5) {
  console.log("while ran:", b);
  b++;
}

You'll only see "do...while ran: 10" in the console. Even though the condition was false from the start, the do block ran once; while never went inside. In real life it suits "try once, then repeat depending on the result" kinds of jobs, like asking a question again until the user gives a valid answer. In practice you'll see it far less often than for and while.

for...of: Looping Over Values

When you walk through an array's elements, most of the time you don't need the index; you just want the values. for...of, which arrived with ES2015, exists exactly for that:

const fruits = ["apple", "pear", "banana"];
for (const fruit of fruits) {
  console.log(fruit);
}

for (const letter of "kiwi") {
  console.log(letter);
}

The first loop prints the three fruits, the second prints the letters "k," "i," "w," "i" in order. No counter, no condition, no length; saying "for everything in this collection" is enough. And there's nowhere left to make a mistake.

Notice that we could use const here. for...of creates a new variable on every lap and binds that lap's value to it; since we never reassign the variable, const fits. In a classic for, on the other hand, you change the counter with i++, so let is a must.

for...of works with structures called iterables, such as arrays and strings. If you try it with a plain object, you get an error like TypeError: user is not iterable. We'll cover the concept of iterables in detail in upcoming posts; for walking through objects, we have another loop.

for...in: An Object's Keys

for...in gives you an object's property names, its keys, one by one:

const user = { name: "Ada", city: "Izmir", age: 31 };
for (const key in user) {
  console.log(key + ": " + user[key]);
}

The output is name: Ada, city: Izmir, age: 31. The loop variable holds the name of the key; to get to the value, we write user[key] with square brackets. We'll cover this notation in detail in the post on objects.

Now back to the opening question. The answer isn't 1, 2, 3; it's 01, 11 and 21. Since an array is also an object, for...in gives you its keys. An array's keys are its indexes, but as strings, not numbers: "0", "1", "2". As we saw earlier, when a string meets a number with +, you get concatenation instead of addition, so "0" + 1 comes out as "01".

That's not the only reason to avoid for...in on arrays. If another property has been added to the array later, it walks over that one too:

const grades = [90, 75, 60];
grades.note = "midterm grades";
for (const key in grades) {
  console.log(key);
}
for (const grade of grades) {
  console.log(grade);
}

The first loop prints 0, 1, 2 and note; the second prints only 90, 75, 60. The rule is short: for...in for objects, for...of for arrays and strings. If you need the index too, use a classic for.

break and continue

Sometimes you don't want to wait until the loop ends. break ends the loop entirely, right then; continue only skips the current lap and moves on to the next:

const numbers = [4, 7, -2, 9, -5];
for (const num of numbers) {
  if (num < 0) {
    console.log("First negative number:", num);
    break;
  }
}

for (let i = 1; i <= 6; i++) {
  if (i % 2 === 0) {
    continue;
  }
  console.log(i);
}

The first loop stops the moment it finds -2; it never looks at 9 and -5. Once you've found what you were looking for, walking through the rest of the list is wasted work. The second loop skips even numbers and prints only 1, 3, 5. The conditions and the % operator we saw earlier work together here.

Use continue sparingly. It's readable in short loops, but a continue hidden in the middle of a long block makes it easy for the reader to miss which lines get skipped.

Infinite Loops and Other Pitfalls

If a loop's condition never becomes false, the loop never ends. Because JavaScript in the browser runs in the same place as the rest of the page, an infinite loop locks up the whole tab: buttons don't respond to clicks, the page doesn't scroll. After a while the browser tells you the page isn't responding and offers to close the tab. If the developer tools are open, you can also try stopping the code with the pause button in the Sources panel. There are three common causes:

Forgetting the step. If you write while (counter < 10) { console.log(counter); } and never increase counter inside, the condition stays true forever. Whenever you write a while, the first thing to ask yourself is where the line that changes the condition is.

Going the wrong way. for (let i = 0; i < 10; i--) decreases the counter, so i always stays below 10. Make sure the condition and the step point in the same direction.

Jumping over the target. for (let i = 0; i !== 10; i += 3) goes to 0, 3, 6, 9 and 12; it never lands on 10, and !== stays true forever. Setting the boundary with < or > instead of equality is safer.

Two more small traps besides these. If you declare the counter of a classic for with const, the first lap runs, then when i++ is attempted you get TypeError: Assignment to constant variable. And writing <= in the condition when walking an array leads you to undefined on the last lap, as we saw above.

If you want to experiment with infinite loops safely, give your loop a seat belt: keep a lap counter and break out once it passes 1000. That way, even if you make a mistake, your tab won't lock up.

A Small Experiment

★☆☆ Use for to calculate the sum of the numbers from 1 to 10 and print the result to the console. Then calculate the same sum with while.

★★☆ Predict the output before running it:

for (let i = 0; i < 10; i += 3) {
  if (i === 6) {
    continue;
  }
  console.log(i);
}

★★☆ Print every key and value of the object const product = { name: "Notebook", price: 40, stock: 12 }; with for...in. Then try to do the same with for...of and read the error you get.

★★★ Walk through the string "hello world" with for...of and count the vowels in it (a, e, i, o, u). When you hit a space, skip that lap with continue. If you stop with break the first time you see the letter "w," what's the result? Predict first, then try it.

We walked through a string letter by letter with for...of, but we haven't seen what strings can really do yet. In the next post we'll focus on strings: quote types, template literals, the most commonly used string methods, and why strings can't be changed in place.