Knowledge Base/concepts/JavaScript for...of Loop

JavaScript for...of Loop

The JavaScript for...of loop is the boring loop you reach for when boring is exactly right. It walks over values from an iterable, supports break and continue, works cleanly with await, and avoids the callback weirdness of forEach. If you need direct control over iteration, for...of is often clearer than forcing everything through an array method.

The key distinction: for...of gives you values. for...in gives you keys.

Basic syntax

for (const value of iterable) {
  // Use value here.
}

Arrays are the most common iterable:

const scores = [88, 74, 95];

for (const score of scores) {
  console.log(score);
}

Use const for the loop variable unless you need to reassign it inside the loop body.

Why not just use forEach?

forEach is fine for simple side effects:

items.forEach((item) => {
  console.log(item);
});

The problem appears when control flow matters:

items.forEach((item) => {
  if (item.disabled) return;
  if (item.priority === "high") return;
  process(item);
});

Those return statements only leave the callback. They do not stop the overall iteration. With for...of, continue and break mean exactly what they look like:

for (const item of items) {
  if (item.disabled) continue;

  if (item.priority === "high") {
    process(item);
    break;
  }
}

For the method-specific comparison, see forEach vs map in JavaScript.

Iterating arrays

Each iteration gives you the next array value:

const users = ["Ada", "Grace", "Katherine"];

for (const user of users) {
  console.log(user.toUpperCase());
}

If you also need the index, use .entries():

for (const [index, user] of users.entries()) {
  console.log(`${index}: ${user}`);
}

That [index, user] syntax is destructuring. It pulls the pair apart at the top of the loop so the body stays clean.

Iterating strings

Strings are iterable:

for (const char of "hello") {
  console.log(char);
}

This is better than manual indexing when you want to process characters as units. String indexing is based on UTF-16 code units, which can split some characters into pieces. for...of iterates strings by code point, which is usually the behavior beginners expect.

Iterating Sets

A Set stores unique values and is iterable:

const tags = new Set(["js", "css", "js"]);

for (const tag of tags) {
  console.log(tag);
}

// "js"
// "css"

The values come out in insertion order.

Iterating Maps

A Map yields [key, value] pairs:

const settings = new Map([
  ["theme", "dark"],
  ["pageSize", 20],
]);

for (const [key, value] of settings) {
  console.log(`${key}: ${value}`);
}

Destructuring is especially useful here. Without it, you would be reading entry[0] and entry[1], which is nobody's idea of clarity.

Plain objects are not iterable

This fails:

const config = {
  host: "localhost",
  port: 3000,
};

for (const value of config) {
  console.log(value);
}

// TypeError: config is not iterable

Plain objects do not implement the iterable protocol by default. Convert the object first:

for (const value of Object.values(config)) {
  console.log(value);
}

If you need keys and values:

for (const [key, value] of Object.entries(config)) {
  console.log(`${key}: ${value}`);
}

This is one of the cleanest object-iteration patterns in modern JavaScript.

for...of versus for...in

for...in iterates enumerable property keys:

const colors = ["red", "green"];

for (const key in colors) {
  console.log(key);
}

// "0"
// "1"

for...of iterates values:

for (const color of colors) {
  console.log(color);
}

// "red"
// "green"

Use for...of for arrays, strings, Sets, Maps, and iterable values. Use Object.keys, Object.values, or Object.entries for plain objects. Reach for for...in rarely and deliberately.

Sequential async work

for...of works naturally with await:

for (const id of userIds) {
  const user = await fetchUser(id);
  await saveUser(user);
}

Each iteration waits before the next starts. That is useful when order matters, when you need rate limiting, or when the next operation depends on the previous one.

Do not use forEach for this:

userIds.forEach(async (id) => {
  const user = await fetchUser(id);
  await saveUser(user);
});

The outer code does not wait for those callbacks to finish.

If you want parallel work, use Promise.all with map:

await Promise.all(userIds.map((id) => fetchUser(id)));

Loop variables and closures

for...of with const or let creates a fresh binding for each iteration. That helps avoid the classic closure problem:

const callbacks = [];

for (const n of [1, 2, 3]) {
  callbacks.push(() => n);
}

console.log(callbacks[0]()); // 1
console.log(callbacks[1]()); // 2
console.log(callbacks[2]()); // 3

Each callback closes over the binding for its own iteration. The Closures guide explains why that matters.

Key Takeaways

  • for...of iterates values from any iterable.
  • It supports break, continue, and sequential await.
  • Plain objects are not iterable; use Object.values() or Object.entries() first.
  • for...of gives values, while for...in gives keys.
  • Use array methods for clean transformations, and use for...of when control flow is the important part.
Share this article:

Practice with 4 related challenges

javascriptfundamentalsarraysloops