Getting started General
Components
Forms
Trends
Utilities
Plugins Sass Migrate from v1
  Join us
  JavaScript Articles

JavaScript for...of Loop Tutorial

JavaScript

Discover why the for...of loop should be your default choice for iteration in JavaScript. Learn how it works with arrays, strings, Maps, Sets, and custom iterables. Master destructuring, explore complementary iterators, and unlock the power of lazy evaluation with generators.

JavaScript for...of loop complete guide
JavaScript for...of Loop: Iterate over arrays, strings, Maps, Sets, and more

JavaScript has always offered multiple ways to loop through data. From the classic numeric for loop inherited from C, to the forEach method, developers have had plenty of options. However, ES2015 introduced a game-changer: the for...of loop.

This loop is still underutilized by many developers, yet it elegantly replaces most traditional iteration patterns. It works seamlessly with arrays, strings, Maps, Sets, NodeLists, and any object that implements the iterable protocol. More importantly, it gives you full control over how much of an iterable you consume, making it perfect for lazy evaluation and infinite sequences.

A Brief History of JavaScript Loops

Before diving into for...of, let's quickly review the loops JavaScript has offered over the years:

The Numeric for Loop

The traditional for loop, borrowed from C and similar languages, uses an index-based approach:

for (var index = 0, len = items.length; index < len; ++index) {
  // Work with items[index]
}

While powerful, this syntax can be verbose and error-prone. You need to manage the index manually, cache the length for performance, and remember to increment the counter.

The for...in Loop

The for...in loop is specifically designed to iterate over enumerable properties of an object:

var person = { first: 'John', last: 'Smith' };
for (var prop in person) {
  console.log(prop, '=', person[prop]);
  // Outputs: 'first' = 'John', 'last' = 'Smith'
}

Note that for...in is not for iterating over array values—it's for object properties. Using it on arrays can lead to unexpected results.

While and do...while Loops

These conditional loops are present in many programming languages. while evaluates the condition before each iteration, while do...while evaluates it after, guaranteeing at least one execution.

What is for...of?

ES2015 formalized a crucial concept: iterables. An iterable is an object that implements the iterable protocol, defined through the Symbol.iterator method. Many built-in JavaScript objects are iterable: arrays, strings, Maps, Sets, NodeLists, and more.

The for...of loop is the primary mechanism for consuming iterables where you have full control. You can consume exactly what you need, and the amount can be determined dynamically or algorithmically.

When working with arrays, you typically care about the values, not the indices. The for...of loop makes this natural:

// Old way: verbose and index-focused
for (var index = 0, len = items.length; index < len; ++index) {
  var item = items[index];
  // Work with item
}

// Modern way: clean and value-focused
for (const item of items) {
  // Work with item directly
}

Like all loops, you can use break, continue, and return within for...of. But it's more versatile than numeric loops: it works even without indices (like with Sets), and you don't need to worry about caching the length.

Using const with for...of

Notice how we used const in the loop variable? That's because we're not reassigning anything—we're working directly with each value as it comes from the iterator. There's no index to manually increment, so const is the natural choice and helps prevent accidental reassignments.

// ✅ Good: const prevents reassignment
for (const item of items) {
  // item is read-only in this scope
}

// ❌ Avoid: let is unnecessary unless you need to reassign
for (let item of items) {
  item = transform(item); // Only affects local variable, not the array
}

Destructuring on the Fly

When your iterator yields multiple values (like entries from a Map), you can destructure them directly in the loop declaration. This makes the code much cleaner:

const map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

// Without destructuring: verbose
for (const pair of map) {
  console.log(pair[0], '=', pair[1]);
}

// With destructuring: clean and readable
for (const [key, value] of map) {
  console.log(key, '=', value);
}

This pattern works with any iterable that yields arrays or tuples. You can destructure as deeply as needed:

const entries = [
  ['user', { id: 1, name: 'Alice' }],
  ['admin', { id: 2, name: 'Bob' }],
];

for (const [role, { id, name }] of entries) {
  console.log(`${role}: ${name} (${id})`);
}

Complementary Iterators

Many iterables provide additional iterators beyond their default iteration. Most offer at least three: keys(), values(), and entries(). For objects without keys (like Sets), the keys are simply the values themselves.

For example, if you want to iterate over an array while also getting the index, you can use entries():

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

// Get both index and value
for (const [index, fruit] of fruits.entries()) {
  console.log(`${index}: ${fruit}`);
}
// Output:
// 0: apple
// 1: banana
// 2: cherry

// Or iterate over just the keys (indices)
for (const index of fruits.keys()) {
  console.log(index);
}
// Output: 0, 1, 2

// Or just the values (same as default)
for (const fruit of fruits.values()) {
  console.log(fruit);
}
// Output: apple, banana, cherry

Maps and Sets also provide these methods, making it easy to work with their specific data structures:

const userMap = new Map([
  ['alice', { role: 'admin' }],
  ['bob', { role: 'user' }],
]);

// Iterate over entries (default)
for (const [username, user] of userMap) {
  console.log(`${username}: ${user.role}`);
}

// Iterate over just keys
for (const username of userMap.keys()) {
  console.log(username);
}

// Iterate over just values
for (const user of userMap.values()) {
  console.log(user.role);
}

Lazy Evaluation: The Real Power

The true strength of for...of lies in its ability to work with lazy evaluation. Because it consumes iterables incrementally rather than all at once, it's the primary way to work with lazy computations, including infinite sequences.

Consider a generator that produces the Fibonacci sequence:

function* fibonacci() {
  let [current, next] = [1, 1];
  while (true) {
    yield current;
    [current, next] = [next, current + next];
  }
}

This sequence never ends. If you try to convert it to an array with Array.from() or the spread operator, your program will hang. However, you can safely consume it with for...of:

// Get first few terms via destructuring
const [a, b, c, d, e] = fibonacci();
// a === 1, b === 1, c === 2, d === 3, e === 5

// Consume until a condition is met
for (const term of fibonacci()) {
  if (term > 100) break;
  console.log(term);
}
// Output: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89

This pattern enables all sorts of lazy evaluation primitives. For example, here's a take function that limits how many items to consume:

function* take(count, iter) {
  if (count === 0) return;
  for (const term of iter) {
    yield term;
    if (--count <= 0) break;
  }
}

// Get first 10 Fibonacci numbers
for (const num of take(10, fibonacci())) {
  console.log(num);
}
// Output: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55

This lazy evaluation approach is fundamental to functional programming concepts and is similar to how libraries like RxJS work with observables.

Performance Considerations

You'll find all sorts of conflicting performance benchmarks online comparing different loop types. Most of them aren't representative of real-world scenarios. Keep these two points in mind:

  • For most arrays (up to millions of elements), the performance difference is negligible.
  • When not transpiled (using native browser support), for...of performs similarly to other loops.

It's extremely rare that you'll need to fall back to a numeric for loop or while for performance reasons. The readability and flexibility benefits of for...of far outweigh any micro-optimizations in the vast majority of cases.

Replacing Old Patterns

You can now replace the vast majority of your traditional iterations with for...of. This includes:

  • Numeric for loops when iterating over arrays
  • forEach() when you need break or continue
  • jQuery/Lodash each() methods
  • for...in when used incorrectly on arrays
// ❌ Old: forEach (can't break early)
items.forEach(item => {
  if (item.shouldStop) return; // Only exits callback, not the loop
  process(item);
});

// ✅ New: for...of (can break early)
for (const item of items) {
  if (item.shouldStop) break; // Exits the entire loop
  process(item);
}

// ❌ Old: jQuery each
$.each(items, function(index, item) {
  // ...
});

// ✅ New: for...of
for (const item of items) {
  // ...
}

Browser Support and Transpilation

The for...of loop is natively supported in:

  • Firefox 13+
  • Chrome 38+
  • Opera 25+
  • Edge 12+
  • Safari 7+
  • Node.js 0.12+

For older environments, Babel and TypeScript can transpile for...of to compatible code. On very large arrays (1M+ elements), transpiled code may have some performance overhead, but this depends heavily on the JavaScript engine, your algorithm, and the context.

Real-World Examples

Example 1: Processing API Responses

async function processUsers() {
  const response = await fetch('/api/users');
  const users = await response.json();
  
  for (const user of users) {
    if (user.status === 'inactive') continue;
    await updateUserProfile(user);
  }
}

Example 2: Working with DOM Collections

// NodeList is iterable
const buttons = document.querySelectorAll('.action-button');

for (const button of buttons) {
  button.addEventListener('click', handleClick);
}

// Works with HTMLCollection too (after conversion)
const divs = Array.from(document.getElementsByTagName('div'));
for (const div of divs) {
  div.classList.add('processed');
}

Example 3: Custom Iterables

class NumberRange {
  constructor(start, end) {
    this.start = start;
    this.end = end;
  }

  *[Symbol.iterator]() {
    for (let i = this.start; i <= this.end; i++) {
      yield i;
    }
  }
}

const range = new NumberRange(1, 5);
for (const num of range) {
  console.log(num);
}
// Output: 1, 2, 3, 4, 5

Best Practices

  • Use const by default: Since you're not reassigning the loop variable, const is the natural choice.
  • Destructure when appropriate: If your iterable yields arrays or objects, destructure them directly in the loop declaration.
  • Use entries() for indices: When you need both index and value from an array, use array.entries().
  • Prefer for...of over forEach: When you need break or continue, for...of is your friend.
  • Leverage lazy evaluation: Use generators and for...of for infinite sequences and on-demand computations.

Conclusion

The for...of loop is one of JavaScript's most elegant and powerful features. It simplifies iteration, works with a wide variety of data structures, and unlocks the power of lazy evaluation through generators.

While it may not be as widely adopted as it should be, for...of should be your default choice for iteration in modern JavaScript. It replaces most numeric loops, eliminates the need for forEach in many cases, and provides a clean, readable syntax that works across all iterable types.

Start using for...of in your code today. Your future self (and your teammates) will thank you for the cleaner, more maintainable code.

Start Building with Axentix

Ready to create amazing websites? Get started with Axentix framework today.

Get Started

Related Posts