skip to content

for...of vs for...in vs forEach

for...in walks enumerable string keys including inherited ones, for...of walks values of an iterable, and forEach is a method that swallows break and await. Choosing wrong produces a whole family of bugs, so interviewers ask you to explain what each one yields over an array.

part ofJavaScriptoverview, primer and where to startread it →
on this pageshow

questions

5

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

open as a page

Inside an `Array.prototype.forEach` callback, what do `return` and `break` actually do, and how do you stop iterating early?

level: middleimportance: must knowfreq 68%

basics

~20 s

return inside a forEach callback only ends that one callback invocation, behaving like continue; break is a SyntaxError because there is no enclosing loop. forEach cannot be stopped early — use for...of with break, or a short-circuiting method like some or find.

open as a page

A migration script runs `ids.forEach(async (id) => { await save(id); });` and then logs "done", but "done" appears before any save finishes and failures never reach the surrounding try/catch. Why does that happen, and how do you fix it?

level: seniorimportance: must knowfreq 62%

basics

~20 s

An async callback returns a promise, and forEach discards its callback's return value, so forEach starts every call and returns undefined immediately without waiting. Rejections surface as unhandled rejections. Use for...of with await for sequential work, or await Promise.all over a mapped array for concurrent work.

open as a page

What happens if you `splice` elements out of an array from inside its own `forEach` callback, and how does a `for...of` loop over the same array behave differently?

level: middleimportance: should knowfreq 42%

basics

~20 s

forEach reads the array's length once before it starts but checks each index as it goes, so removing an element shifts the rest down and one gets skipped per removal; appended elements are never visited. for...of re-reads length on every step, so it does see appends — which makes a loop that pushes run forever.

open as a page

When you use `for...in` over a plain object, which keys can show up that you never put on that object, and how do you filter them out?

level: middleimportance: should knowfreq 52%

basics

~20 s

for...in visits enumerable string keys from the object's prototype chain as well as its own, so anything enumerable on a custom prototype or added to a built-in prototype appears. Guard each key with Object.hasOwn(obj, key), or skip for...in and use Object.keys, values, or entries.

open as a page