skip to content

Iterators and Generators

Iteration in JavaScript is a protocol, not a built-in privilege of arrays: anything that implements Symbol.iterator works with for...of, spread, and destructuring. Generators then let you write that protocol as ordinary-looking code that pauses at each yield, which is why interviewers use them to probe lazy evaluation.

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

questions

19

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

In JavaScript, what happens when you call a generator function declared with `function*` — does its body run, and what do you get back?

level: juniorimportance: must knowfreq 55%

basics

~20 s

Calling a generator function runs none of its body. It returns a generator object — a paused iterator. The body advances only when you call next(), running until the next yield and then suspending with its local state intact.

open as a page

In JavaScript, what makes a value iterable, and what must the object returned by its Symbol.iterator method look like?

level: juniorimportance: must knowfreq 70%

basics

~20 s

A value is iterable if it has a Symbol.iterator method that returns an iterator — an object whose next() method returns { value, done }. Arrays, strings, Map and Set have one; plain objects do not.

open as a page

In JavaScript, you need the first five results of mapping and filtering an unbounded sequence of numbers. Why do chained array methods fail here, and how does a generator pipeline solve it?

level: middleimportance: must knowfreq 50%

basics

~20 s

Array map and filter are eager: they need a finished array and build a new one at every stage, so an unbounded source never gets past the first call. Generators are pull-based, so a take stage requests only the five values it needs.

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

Why does [...user] throw a TypeError when user is a plain object, while {...user} copies its properties without complaint?

level: middleimportance: must knowfreq 60%

basics

~20 s

Array spread runs the iteration protocol and a plain object has no Symbol.iterator method, so it throws. Object spread does not iterate at all — it copies the source's own enumerable properties onto the new object.

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

In a JavaScript generator function, what is the difference between yield [1, 2, 3] and yield* [1, 2, 3], and what kinds of operands does yield* accept?

level: juniorimportance: should knowfreq 45%

basics

~20 s

yield [1, 2, 3] produces one value: the array itself. yield* iterates its operand and produces each element separately, so the consumer sees 1, then 2, then 3. yield* accepts any iterable, not only generators.

open as a page

In JavaScript, a generator delegates with const result = yield* inner(). What does result end up holding, and what does the caller of the outer generator see?

level: middleimportance: should knowfreq 40%

basics

~20 s

result holds the inner generator's return value, the value carried by its final {value, done: true} result. Everything the inner generator yields goes straight through to the caller; only the return value flows back into the delegating generator.

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

Inside a JavaScript generator, what value does a `yield` expression evaluate to, and what happens to the argument passed to the generator's very first `next()` call?

level: middleimportance: should knowfreq 45%

basics

~20 s

A yield expression evaluates to the argument of the next() call that resumes it, which is how a caller pushes data back into a running generator. The first next() has no suspended yield waiting for a value, so its argument is discarded.

open as a page

Given the same argument x, what can Array.from(x) do that the array spread [...x] cannot?

level: middleimportance: should knowfreq 45%

basics

~10 s

Array.from also accepts array-likes — objects with a length and indexed properties but no Symbol.iterator — and takes an optional mapping function as its second argument. Array spread only accepts iterables and cannot map.

open as a page

How do you make instances of your own JavaScript class work with for...of and array spread, without using a generator function?

level: middleimportance: should knowfreq 50%

basics

~20 s

Add one method to the class, keyed with the computed name [Symbol.iterator], that returns a fresh iterator object each call — an object whose next() returns { value, done } and whose loop state lives in local variables.

open as a page

In JavaScript, when does replacing a chain of array map and filter calls with a generator pipeline actually pay off in production, and what do you give up by doing it?

level: seniorimportance: should knowfreq 33%

basics

~20 s

Generator pipelines pay off when the source is unbounded or far larger than what you consume, when work per item is expensive, or when you exit early. You give up speed on small inputs, random access and length, re-iterability, and easy debugging.

open as a page

In JavaScript, how do you lazily walk a deeply nested structure with a recursive generator, and what goes wrong when the nesting gets very deep?

level: seniorimportance: should knowfreq 30%

basics

~20 s

A generator that calls yield* on itself for each child flattens any nesting lazily in a few lines. The cost is that every value is relayed through one next() call per nesting level, so deep structures get slow and can hit the call-stack limit.

open as a page

A `for...of` loop over a JavaScript generator hits `break` before the generator is exhausted. What does the language do to the suspended generator, and how do you guarantee its cleanup runs?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Leaving the loop early calls the generator's return() method, which resumes the body as though a return statement ran at the paused yield. Any enclosing finally block executes, so put resource cleanup in try/finally — code after the last yield never runs on early exit.

open as a page

A JavaScript array can be looped with for...of repeatedly, but the object returned by a Map's values() method yields nothing on a second pass. Why?

level: seniorimportance: should knowfreq 35%

basics

~10 s

An array's Symbol.iterator returns a brand-new iterator on every call, so each loop starts fresh. A Map iterator answers Symbol.iterator by returning itself, so a second loop resumes the same already-exhausted cursor.

open as a page

What does calling `gen.throw(new Error('boom'))` do to a paused JavaScript generator, and what happens if the generator has not started running yet?

level: middleimportance: nice to knowfreq 25%

basics

~20 s

throw() resumes the generator by raising the error at the paused yield, so a try/catch inside the body can handle it and the generator may keep going. If the generator has not started, or has already finished, no body code runs and the error propagates back to the caller.

open as a page