skip to content

Given `const arr = ['a', 'b', 'c']`, what does `for (const x in arr)` give you on each pass compared with `for (const x of arr)`, and why is for...in the wrong loop for arrays?

level: juniorimportance: must knowfreq 78%

answer

  1. keys versus values
  2. the loop variable's type matters
  3. string "0" is not number 0
  4. inherited enumerable properties come too
  5. Object.entries is the object-side answer

basics

~20 s

for...in yields property keys as strings — "0", "1", "2" — plus any other enumerable property on the array or its prototype chain. for...of yields the values "a", "b", "c". Arrays hold values, so for...of is the right loop.

solid answer

~50 s

`for...in` is a **property** loop: it walks the enumerable string-keyed properties of the object and of everything on its prototype chain. An array is just an object with index-like keys, so `for (const x in arr)` binds the strings `"0"`, `"1"`, `"2"` — and also any extra property someone attached, like `arr.tag = 'meta'`, or an enumerable property added to `Array.prototype`. `for...of` is a **value** loop: it asks the array for its iterator and binds each element, so you get `"a"`, `"b"`, `"c"`. The practical damage from using `for...in` on an array is that the index is a string, so `x + 1` gives `"01"` instead of `1`, and non-index properties leak into the loop. If you need both position and value, use `for (const [i, v] of arr.entries())`, which gives a real number index.

code

javascript · 11 lines
javascript
const arr = ['a', 'b', 'c'];
arr.tag = 'meta';

for (const k in arr) console.log('in:', typeof k, k);
// in: string 0 / in: string 1 / in: string 2 / in: string tag

for (const v of arr) console.log('of:', v);
// of: a / of: b / of: c

for (const [i, v] of arr.entries()) console.log('entries:', typeof i, i, v);
// entries: number 0 a / ...

go deeper

for a junior

Be able to say instantly that for...in gives keys and for...of gives values, and that arrays should use for...of. Know that the for...in key arrives as a string.

for a middle

Explain why an array's indices are string property keys at all, that for...in walks the prototype chain, and that Object.keys/values/entries plus for...of is the object-side equivalent.

for a senior

Show the failure mode in real code: string-index arithmetic, a stray property on an array object, or a polluted prototype leaking keys into every loop in the program. Say how you would catch it in review or lint.

for a principal

Frame it as a codebase-wide convention: banning for...in via lint and standardizing on for...of plus Object.entries removes a whole bug class and makes iteration behaviour independent of what anyone attaches to prototypes.

## Two loops that look alike and are not JavaScript has two `for` loops that read almost identically and mean completely different things. `for...in` enumerates **property keys**. `for...of` iterates **values produced by an iterator**. The single letter is the whole difference, and mixing them up is one of the most common bugs in early JavaScript code. ## What for...in actually enumerates `for...in` visits the *enumerable, string-keyed* properties of an object — the object's own properties **and** the enumerable properties it inherits through its prototype chain. Symbol-keyed properties are never visited. An array in JavaScript is an ordinary object whose elements live under keys that happen to look like integers. Object property keys are always strings (or symbols), so the array `['a','b','c']` really has properties named `"0"`, `"1"`, `"2"` plus a `length` property. `length` is non-enumerable, so `for...in` skips it, but the indices are enumerable and get visited — as strings: ```js const arr = ['a', 'b', 'c']; for (const k in arr) console.log(typeof k, k); // string 0, string 1, string 2 ``` Because it is a property loop, it also picks up anything else you put on the array object: ```js arr.tag = 'meta'; for (const k in arr) console.log(k); // 0, 1, 2, tag ``` And anything enumerable that lives further up the chain. Built-in `Array.prototype` methods are non-enumerable, so a clean environment shows only your keys, but a script that does `Array.prototype.last = function () {...}` (an old, discouraged pattern) makes `last` appear in every `for...in` over every array in the program. ## What for...of actually iterates `for...of` does not look at properties at all. It asks the object for an iterator and consumes the values that iterator produces, so over an array you get the elements themselves: ```js for (const v of arr) console.log(v); // a, b, c ``` Because it works off iteration rather than property keys, `for...of` also works over strings, `Map`, `Set`, `arguments`, and DOM collections — and it does **not** work over a plain object literal, which produces `TypeError: obj is not iterable`. ## The string-index trap This is what usually surfaces the bug in review: ```js const nums = [10, 20, 30]; let sum = 0; for (const i in nums) sum += i; // sum === '0' + '1' + '2' -> '012' ``` `+` with a string operand concatenates, so the arithmetic silently produces a string. Confusingly, `nums[i]` still works, because bracket property access converts whatever you give it to a string anyway — which is why `for (const i in nums) sum += nums[i]` looks fine and lulls people into keeping the loop. ## Order For arrays, engines visit integer-like own keys in ascending numeric order, so `for...in` *looks* ordered. Once you add non-index properties or inherit from a custom prototype, the overall enumeration order stops being something worth relying on. `for...of` over an array is always index order, front to back, by definition of the array iterator. ## What to use instead, per shape - Array values: `for (const v of arr)`. - Array index **and** value: `for (const [i, v] of arr.entries())` — `i` is a number here. - Plain object keys: `for (const k of Object.keys(obj))`. - Plain object values: `for (const v of Object.values(obj))`. - Plain object pairs: `for (const [k, v] of Object.entries(obj))`. ```js const config = { host: 'localhost', port: 8080 }; for (const [k, v] of Object.entries(config)) console.log(k, v); ``` These `Object.*` helpers return only **own**, enumerable, string-keyed properties, so they sidestep the inherited-key problem that `for...in` has entirely. That is the reason most modern style guides say: use `for...of` with `Object.keys`/`values`/`entries` for objects, and reserve `for...in` for the rare case where you genuinely want the inherited keys too. ## The one-line summary an interviewer wants "`for...in` gives me keys, as strings, including inherited ones; `for...of` gives me values from an iterator. Arrays want `for...of`; plain objects want `Object.entries` plus `for...of`."

  • If the index from for...in is a string, why does `total += nums[i]` still add the right numbers?
    Because bracket property access converts its argument to a property key string regardless. `nums["1"]` and `nums[1]` reach the same property, so element lookup works. Only arithmetic on the loop variable itself — `i + 1`, `i * 2`, `sum += i` — exposes that `i` is a string. That is exactly why the bug survives code review: the common usage hides it.
  • What happens if you run for...of over a plain object literal, and what do you use instead?
    It throws `TypeError: obj is not iterable`, because a plain object has no iterator. Use `for (const k of Object.keys(obj))` for keys, `Object.values(obj)` for values, or `for (const [k, v] of Object.entries(obj))` for both. All three return only own, enumerable, string-keyed properties, so nothing inherited leaks in.
  • How do you get both the index and the value while still using for...of?
    Destructure `arr.entries()`: `for (const [i, v] of arr.entries()) { ... }`. `entries()` returns an iterator of `[index, value]` pairs, and unlike `for...in` the index is a real number, so arithmetic on it behaves. `keys()` and `values()` exist too if you only need one half.

saying these in an interview costs you the question

  • Says for...in gives you the array elements
  • Assumes the for...in index is a number
  • Uses for...in over arrays as normal practice
  • Thinks for...of iterates plain object properties
  • Claims for...in only ever sees own properties

context