JavaScriptfor...of & for...in

for...of vs for...in

JavaScript has two specialised for variants — for...of and for...in — and they do completely different things. One walks values, the other walks keys. Picking the wrong one is a famous beginner trap, especially on arrays.

The shape of each

JS
for (const value of iterable)  { /* value is each element */ }
for (const key of   enumerable) { /* WRONG — for...in uses 'in' */ }
for (const key   in object)     { /* key is each enumerable property name */ }

The shapes are nearly identical — one word changes meaning entirely. Read those keywords carefully every time.

for...of: iterables

for...of consumes any iterable — anything that implements the iterator protocol. That includes arrays, strings, Map, Set, NodeList, arguments, generators, and typed arrays. You get the values, not the indexes.

JS
const items = ["a", "b", "c"];

for (const item of items) {
  console.log(item);
}
a
b
c

Need the index too? entries() pairs them up:

JS
for (const [index, item] of items.entries()) {
  console.log(index, item);
}
0 a
1 b
2 c

Strings are iterable code-point by code-point — so for...of handles emoji and other non-BMP characters correctly, unlike indexed access.

JS
for (const ch of "ab🦊") {
  console.log(ch);
}
a
b
🦊
for...in: enumerable property keys

for...in walks the enumerable string keys of an object — own properties and anything inherited from the prototype chain that hasn't been hidden. You get key names as strings.

JS
const user = { name: "Ada", age: 36, role: "engineer" };

for (const key in user) {
  console.log(key, user[key]);
}
name Ada
age 36
role engineer

If Object.prototype has been extended (libraries occasionally do this), those properties show up too. The defensive idiom is Object.hasOwn(user, key):

JS
for (const key in user) {
  if (!Object.hasOwn(user, key)) continue;
  console.log(key, user[key]);
}
The classic for...in array pitfall

for...in was never meant for arrays. Three things go wrong when you use it that way:

  • You get strings, not numbers. "0" + 1 is "01", not 1. Arithmetic on indexes silently breaks.

  • Order is not guaranteed for integer keys in every engine — though modern ones agree on ascending-integer order, you shouldn't rely on it.

  • Anything added to Array.prototype (legacy libraries, polyfills) becomes an extra iteration.

The bug

JS
Array.prototype.last = function () { return this[this.length - 1]; };

const nums = [10, 20, 30];

for (const i in nums) {
  console.log(i, nums[i]);
}
0 10
1 20
2 30
last [Function: last]
Don't use for...in on arrays
Use `for...of` for values, `forEach` for callbacks, or a classic `for` when you need the index. `for...in` belongs on plain objects.
Side-by-side decision table
  • Walking an array for values → for...of.

  • Walking an array with index → for (let i = 0; ...) or arr.entries() with for...of.

  • Walking an object for its own keys → for...in with Object.hasOwn, or for (const key of Object.keys(obj)).

  • Walking a Map or Setfor...of. Maps yield [key, value] pairs.

  • Walking a string by character → for...of.

  • Walking a generator or other iterator → for...of.

Object.keys, values, entries
On plain objects, `for...in` is rarely the cleanest choice. `for (const key of Object.keys(obj))` is unambiguous, ignores prototype chain noise, and keeps you in the `for...of` family.
Maps, Sets and other iterables

JS
const scores = new Map([
  ["alice", 90],
  ["bob", 85],
]);

for (const [name, score] of scores) {
  console.log(name, score);
}

const ids = new Set([1, 2, 3]);
for (const id of ids) {
  console.log(id);
}

Iteration order for Map and Set is insertion order — predictable and useful.

Breaking out

Both for...of and for...in support break, continue and return. That makes them better than forEach whenever you want to stop early.

JS
function findUser(users, predicate) {
  for (const user of users) {
    if (predicate(user)) return user;     // early exit — clean
  }
  return null;
}
Why the names matter

Remember the two keywords by what comes after them: in for-of-iterable you're saying "give me each of the values", in for-in-object you're asking "what's in this object as keys?". Once that lands, the choice becomes automatic.

One sentence to remember
`for...of` for values, `for...in` for plain-object keys, and *never* `for...in` on an array.