JavaScript For In For Of

JavaScript gives you two loop constructs that look almost identical but do very different jobs: for...in walks over the enumerable property names (keys) of an object, while for...of walks over the values produced by an iterable. Confusing the two is one of the most common sources of subtle bugs for people coming from other languages, especially when looping over arrays. This lesson explains exactly what each loop visits, how the mechanism works internally, and when to reach for one over the other.

Overview: How for…in and for…of Work

for...in is a property enumerator. Given an object, it produces the names (as strings) of that object’s enumerable properties, one per iteration — but not just its own properties. It also walks up the prototype chain and includes any enumerable properties inherited from prototypes. This is why looping over an array with for...in can surprise you: an array’s numeric indexes (“0”, “1”, “2”…) are enumerable own properties, so they show up, but so would any enumerable property someone accidentally added to Array.prototype.

for...of is completely different: it does not care about property names or enumerability at all. It works with the iterable protocol. An object is iterable if it has a method keyed by the well-known symbol Symbol.iterator that returns an iterator — an object with a next() method that returns { value, done } pairs. Arrays, strings, Map, Set, NodeList, the arguments object, and generator objects are all iterable out of the box. Plain object literals ({}) are not iterable by default, which is the single most common reason beginners see “is not iterable” errors.

So the mental model is simple: use for...in when you care about an object’s keys, and use for...of when you care about a collection’s values.

Syntax

for (const key in object) {
  // key is always a string, even for array indexes
  console.log(key, object[key]);
}

for (const value of iterable) {
  // value is whatever the iterator yields
  console.log(value);
}
Aspect for…in for…of
Iterates over Enumerable property keys (own + inherited) Values produced by an iterator
Works on Any object Only iterables (Array, String, Map, Set, etc.)
Loop variable type Always a string Whatever type the iterable yields
Order Integer-like keys first (ascending), then insertion order for the rest Defined by the iterator (arrays & strings: index order)
Typical use Inspecting plain object properties Looping over arrays, strings, Maps, Sets, generators

Examples

Example 1: for…of over an array

const colors = ['red', 'green', 'blue'];

for (const color of colors) {
  console.log(color);
}
Output:
red
green
blue

Each pass of the loop asks the array’s iterator for the next value and binds it directly to color. There is no index bookkeeping to write yourself, and the values come out in the array’s natural order.

Example 2: for…in over a plain object

const car = {
  make: 'Toyota',
  model: 'Corolla',
  year: 2022
};

for (const key in car) {
  console.log(`${key}: ${car[key]}`);
}
Output:
make: Toyota
model: Corolla
year: 2022

Here key is a string on every pass (“make”, “model”, “year”), and you look the value up yourself with car[key]. This is exactly the scenario for...in is designed for: enumerating the properties of a plain object.

Example 3: for…of with Map entries and array entries()

const inventory = new Map([
  ['apples', 5],
  ['bananas', 12],
  ['cherries', 30]
]);

for (const [item, quantity] of inventory) {
  console.log(`${item}: ${quantity}`);
}

const fruits = ['apple', 'banana', 'cherry'];
for (const [index, fruit] of fruits.entries()) {
  console.log(`${index} -> ${fruit}`);
}
Output:
apples: 5
bananas: 12
cherries: 30
0 -> apple
1 -> banana
2 -> cherry

A Map iterates as [key, value] pairs by default, so destructuring them directly in the loop header is idiomatic. Arrays don’t hand you an index automatically with for...of, but calling .entries() gives you an iterable of [index, value] pairs, which is the standard way to get both when you need one.

Under the Hood: The Two Protocols

For for...of, the engine performs, conceptually, these steps for for (const v of iterable) { body }:

  1. Call iterable[Symbol.iterator]() to obtain an iterator object.
  2. Call iterator.next(), which returns { value, done }.
  3. If done is true, stop looping.
  4. Otherwise bind value to v, run body, then go back to step 2.
  5. If the loop exits early — via break, return, or a thrown error — the engine calls iterator.return() if that method exists, giving the iterable a chance to clean up (close a file handle, release a lock, etc.).

For for...in, there is no iterator object at all. The engine builds a list of enumerable property keys by inspecting the object itself and then walking up its prototype chain, skipping any key that has already been seen closer to the object (so shadowed inherited properties aren’t listed twice). Per the specification, integer-like keys are visited first in ascending numeric order, followed by string keys in the order they were created, followed by inherited enumerable keys in the same fashion. Because this list depends on the entire prototype chain, adding an enumerable property to a built-in prototype (like Array.prototype) affects every for...in loop over every array in your program — not just the one you meant to change.

Common Mistakes

Mistake 1: Using for…in on arrays

Array.prototype.extra = 'oops';

const nums = [10, 20, 30];

for (const key in nums) {
  console.log(key, nums[key]);
}

delete Array.prototype.extra;
Output:
0 10
1 20
2 30
extra oops

Adding any enumerable property to Array.prototype (something a careless library or polyfill might do) makes it show up in every for...in loop over every array, mixed in with the real indexes. The fix is to never use for...in on arrays: use for...of, or filter to own properties only:

Array.prototype.extra = 'oops';

const nums = [10, 20, 30];

for (const key in nums) {
  if (Object.hasOwn(nums, key)) {
    console.log(key, nums[key]);
  }
}

delete Array.prototype.extra;
Output:
0 10
1 20
2 30

Object.hasOwn(nums, key) filters out anything inherited from the prototype chain, leaving only the array’s own indexes.

Mistake 2: Using for…of on a plain object

const person = { name: 'Ada', age: 36 };

try {
  for (const value of person) {
    console.log(value);
  }
} catch (error) {
  console.log(error.message);
}
Output:
person is not iterable

Plain objects don’t implement Symbol.iterator, so for...of throws a TypeError immediately. If you want to loop over an object’s values (or keys, or both) with for...of, convert it to an iterable first with Object.keys(), Object.values(), or Object.entries():

const person = { name: 'Ada', age: 36 };

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

Best Practices

  • Use for...of for arrays, strings, Maps, Sets, and any other iterable when you need the actual values.
  • Never use for...in on arrays or array-likes — the key is a string, order isn’t guaranteed by the array’s design, and inherited properties can leak in.
  • When you need to loop over a plain object’s properties with for...of, wrap it with Object.entries(), Object.keys(), or Object.values() instead of reaching for for...in.
  • If you must use for...in, guard the body with Object.hasOwn(obj, key) (or the older obj.hasOwnProperty(key)) to ignore inherited properties.
  • Prefer array methods (map, filter, reduce, forEach) over manual loops when you’re transforming or summarizing data functionally — reach for for...of when you need break/continue, await inside the loop, or an early return.
  • Avoid mutating a collection’s length while iterating it with either loop; adding or removing elements mid-loop can skip or duplicate entries.

Practice Exercises

  • Write a function that takes an array of numbers and uses for...of to return their sum.
  • Given const scores = { alice: 92, bilal: 85, carla: 78 };, use for...in to log a line like “alice: 92” for each student, then rewrite it using Object.entries() with for...of instead.
  • Predict, then explain, what happens when you run for (const x of { length: 3 }), and describe one way to make that object iterable.

Summary

  • for...in enumerates an object’s enumerable property keys, including ones inherited through the prototype chain.
  • for...of iterates the values produced by any object that implements the iterable protocol (Symbol.iterator).
  • Arrays, strings, Maps, Sets, and generators are iterable; plain objects are not, so for...of throws a TypeError on them.
  • Use Object.entries()/keys()/values() to bridge plain objects into for...of.
  • Avoid for...in on arrays — use for...of, .entries(), or array methods instead.