JavaScript Loops: for, while, for…of, for…in Explained with Examples
Table of Contents
JavaScript loops let you execute code repeatedly without writing it out dozens of times. Whether you’re processing arrays, reading API responses, or building game ticks, loops are one of the most fundamental constructs you’ll use daily. This guide covers every loop type in JavaScript — from the classic for loop to the modern for...of — with practical patterns, performance tips, and the gotchas that trip up even experienced developers.
If you haven’t covered JavaScript Conditionals yet, start there — loops and conditionals work hand-in-hand.
Why Loops Matter in JavaScript
Imagine you have an array of 1,000 users and need to send each one a notification. Without loops, you’d need 1,000 lines of nearly identical code. Loops solve this by letting you write the logic once and run it as many times as needed. They’re used everywhere: rendering UI lists, processing form inputs, searching data structures, running animations, and building algorithms.
JavaScript provides several loop types, each suited to different situations. The key is knowing which one to reach for — and when to use array methods instead.
The for Loop
The for loop is the workhorse of JavaScript iteration. It gives you complete control over initialization, condition checking, and incrementing.
for (let i = 0; i < 5; i++) {
console.log(i); // 0, 1, 2, 3, 4
}
The three parts inside the parentheses are: initialization (let i = 0), condition (i < 5), and update (i++). All three are optional — omitting the condition creates an infinite loop (you’d use break to exit).
Counting Backwards
for (let i = 10; i >= 0; i--) {
console.log(i); // 10, 9, 8, ... 0
}
Stepping by More Than One
for (let i = 0; i < 100; i += 10) {
console.log(i); // 0, 10, 20, ... 90
}
Multiple Variables
for (let i = 0, j = 10; i < j; i++, j--) {
console.log(i, j); // 0 10, 1 9, 2 8, 3 7, 4 6
}
Iterating Arrays with for
const fruits = ['apple', 'banana', 'cherry'];
for (let i = 0; i < fruits.length; i++) {
console.log(fruits[i]);
}
For large arrays, cache the length to avoid recalculating it every iteration:
for (let i = 0, len = fruits.length; i < len; i++) {
console.log(fruits[i]);
}
while and do…while
The while loop checks its condition before each iteration. If the condition is false initially, the body never executes.
let count = 0;
while (count < 5) {
console.log(count);
count++;
}
The do...while loop guarantees the body runs at least once because it checks the condition after each iteration:
let input;
do {
input = prompt('Enter "yes" to continue:');
} while (input !== 'yes');
This is perfect for menu systems, retry logic, or any situation where you need at least one execution.
When to Use while vs for
Use while when you don’t know how many iterations you need ahead of time — reading from a stream, waiting for a condition, or processing until a queue is empty. Use for when you know the count or are iterating a collection with indices.
for…in — Iterating Object Keys
The for...in loop iterates over the enumerable string properties of an object, including inherited ones:
const user = { name: 'Chirag', role: 'admin', level: 42 };
for (const key in user) {
console.log(key, user[key]);
}
// name Chirag
// role admin
// level 42
The Prototype Pitfall
function Person(name) { this.name = name; }
Person.prototype.species = 'human';
const p = new Person('Alice');
for (const key in p) {
console.log(key); // name, species (inherited!)
}
To skip inherited properties, use hasOwnProperty:
for (const key in p) {
if (p.hasOwnProperty(key)) {
console.log(key); // name only
}
}
Or better, use Object.keys(), Object.values(), or Object.entries() which only return own properties.
Never Use for…in on Arrays
While it technically works, for...in on arrays iterates string indices and includes non-numeric properties. The order isn’t guaranteed in older engines. Always use for...of or a standard for loop for arrays.
for…of — Iterating Iterables
Introduced in ES6, for...of iterates over iterable objects: arrays, strings, Maps, Sets, NodeLists, generators, and anything implementing the iterable protocol.
const colors = ['red', 'green', 'blue'];
for (const color of colors) {
console.log(color); // red, green, blue
}
Strings
for (const char of 'Hello') {
console.log(char); // H, e, l, l, o
}
Maps and Sets
const map = new Map([['a', 1], ['b', 2]]);
for (const [key, value] of map) {
console.log(key, value); // a 1, b 2
}
const set = new Set([10, 20, 30]);
for (const num of set) {
console.log(num); // 10, 20, 30
}
Getting the Index with entries()
const fruits = ['apple', 'banana', 'cherry'];
for (const [index, fruit] of fruits.entries()) {
console.log(index, fruit);
}
NodeLists (DOM)
const paragraphs = document.querySelectorAll('p');
for (const p of paragraphs) {
p.style.color = 'blue';
}
break and continue
The break statement exits the loop entirely. The continue statement skips to the next iteration.
// Find the first negative number
const nums = [3, 7, -2, 5, -8];
for (const n of nums) {
if (n < 0) {
console.log('Found:', n); // Found: -2
break;
}
}
// Skip even numbers
for (let i = 0; i < 10; i++) {
if (i % 2 === 0) continue;
console.log(i); // 1, 3, 5, 7, 9
}
Labeled Statements
Labels let you break or continue an outer loop from inside a nested loop:
outer: for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (i === 1 && j === 1) break outer;
console.log(i, j);
}
}
// 0 0, 0 1, 0 2, 1 0
Labels are rarely used in modern JavaScript — most developers prefer extracting the logic into a function and using return instead. But they’re useful for algorithm-heavy code like matrix searches.
Nested Loops and Performance
Nested loops multiply complexity. A loop inside a loop on the same data gives you O(n²) time complexity:
// O(n²) — find duplicate pairs
const arr = [1, 2, 3, 2, 4, 1];
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j]) {
console.log(`Duplicate: ${arr[i]} at ${i} and ${j}`);
}
}
}
For large datasets, consider using a Set or Map to reduce to O(n):
// O(n) — find duplicates with Set
const seen = new Set();
for (const num of arr) {
if (seen.has(num)) console.log('Duplicate:', num);
seen.add(num);
}
Common Loop Patterns
Accumulator Pattern
const prices = [29.99, 9.99, 49.99, 19.99];
let total = 0;
for (const price of prices) {
total += price;
}
console.log(total); // 109.96
Search Pattern
function findUser(users, id) {
for (const user of users) {
if (user.id === id) return user;
}
return null;
}
Filter Pattern
const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evens = [];
for (const n of nums) {
if (n % 2 === 0) evens.push(n);
}
// evens: [2, 4, 6, 8, 10]
Transformation Pattern
const names = ['alice', 'bob', 'charlie'];
const upper = [];
for (const name of names) {
upper.push(name.toUpperCase());
}
Flatten Pattern
const nested = [[1, 2], [3, 4], [5, 6]];
const flat = [];
for (const arr of nested) {
for (const num of arr) {
flat.push(num);
}
}
// flat: [1, 2, 3, 4, 5, 6]
Avoiding Infinite Loops
An infinite loop runs forever, freezing your browser tab or crashing Node.js. Common causes:
// Bug: i is never incremented
for (let i = 0; i < 10; ) {
console.log(i); // infinite!
}
// Bug: condition never becomes false
let x = 10;
while (x > 0) {
console.log(x);
x++; // should be x--
}
Prevention tips: always ensure the loop variable moves toward the exit condition, add a safety counter during development, and use for...of when possible since it handles termination automatically.
Loops vs Array Methods
Modern JavaScript offers array methods like forEach, map, filter, and reduce that often replace loops:
// Loop
const doubled = [];
for (const n of [1, 2, 3]) {
doubled.push(n * 2);
}
// Array method (preferred for transforms)
const doubled2 = [1, 2, 3].map(n => n * 2);
When to use loops over array methods:
- You need
breakorcontinue(forEachdoesn’t support them) - You’re iterating something that isn’t an array (DOM NodeLists, Maps, generators)
- You need the index and value from a non-array iterable
- Performance-critical code (loops can be marginally faster)
- You’re building complex logic with multiple exit points
Loops with Async/Await
A common trap: forEach doesn’t wait for async callbacks. Use for...of instead:
// WRONG — fires all requests simultaneously
urls.forEach(async (url) => {
const data = await fetch(url); // doesn't wait!
});
// RIGHT — sequential execution
for (const url of urls) {
const response = await fetch(url);
const data = await response.json();
console.log(data);
}
// PARALLEL — if order doesn't matter
const results = await Promise.all(
urls.map(url => fetch(url).then(r => r.json()))
);
Best Practices
Use const in for...of and for...in when you don’t reassign the variable. Use let in classic for loops. Never use var — it doesn’t create a new binding per iteration, causing closure bugs. Prefer for...of for arrays and iterables, for...in only for objects (with caution), and standard for when you need index control. Extract complex loop bodies into functions to keep code readable.
Next up: JavaScript Conditionals showed you how to make decisions — now that you can loop, learn how to handle things when they go wrong in our Error Handling guide.