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?
answer
- both are iterable, differently
- one hands back a new cursor
- the other hands back itself
- %IteratorPrototype% returns this
- empty second pass, no error
basics
~10 sAn 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.
solid answer
~40 sBoth values satisfy the iterable protocol, but they answer it differently. `Array.prototype[Symbol.iterator]()` constructs a *new* iterator each time it is called, so every `for...of` starts at index zero. `map.values()` returns an iterator object, and built-in iterators inherit from `%IteratorPrototype%`, whose `[Symbol.iterator]()` simply returns `this`. So looping it a second time hands the loop the same object, whose cursor is already past the end — it reports `done: true` immediately and the body never runs. No error is thrown, which is what makes it a quiet bug. The practical rule: a *collection* is re-iterable, an *iterator* is single-use. If a function takes an iterable and needs more than one pass, materialise it once with `Array.from(input)` and work with the array, or take a factory that produces a fresh iterable per call.
code
javascript · 10 linesconst m = new Map([['a', 1], ['b', 2]]);
const values = m.values();
console.log([...values]); // [1, 2]
console.log([...values]); // [] — same cursor, exhausted
console.log(values[Symbol.iterator]() === values); // true
const arr = [1, 2];
console.log(arr[Symbol.iterator]() === arr); // false
console.log([...arr], [...arr]); // [1,2] [1,2]go deeper
Know that some iterable values can be looped only once, and that spreading a Map's values() into an array first gives you something you can use repeatedly.
Explain the mechanism: an array's Symbol.iterator builds a new cursor per call, while a built-in iterator's Symbol.iterator returns this. Say clearly that the exhausted second pass is silent, not an error.
Diagnose a blank second pass back to a single-use iterator, and choose the fix deliberately — materialise at the boundary, restructure to one pass, or take a factory — with the memory and streaming tradeoff stated.
Set the contract at API boundaries: whether shared code accepts iterables at all, whether re-iteration is promised, and how that promise is documented and tested. Ambiguity here produces silent data-loss bugs that no type signature catches.
## The same protocol, two different answers Both an array and the object returned by `map.values()` pass the "is it iterable" test — both have a `Symbol.iterator` method. What differs is what that method *returns*. - `Array.prototype[Symbol.iterator]()` allocates and returns a new Array Iterator, positioned at index 0. Ten calls give ten independent cursors. - `map.values()` returns a Map Iterator. Map iterators inherit from `%MapIteratorPrototype%`, which inherits from `%IteratorPrototype%`, and that shared prototype defines `[Symbol.iterator]() { return this; }`. The object *is* the cursor and hands itself back. So the second `for...of` over an array asks for a cursor and gets a fresh one; the second `for...of` over `map.values()` asks for a cursor and gets the same, spent one. ```js const m = new Map([['a', 1], ['b', 2]]); const values = m.values(); [...values]; // [1, 2] [...values]; // [] — same cursor, already at the end const arr = [1, 2]; [...arr]; // [1, 2] [...arr]; // [1, 2] ``` Crucially the second pass does not throw. It produces an empty result, which flows onward and fails somewhere else — an empty render, a zero total, a `reduce` that hits "reduce of empty array with no initial value". ## Why iterators are iterable at all It would be simpler if iterators were not iterable, but then `for (const v of map.values())` would need extra ceremony. Making `%IteratorPrototype%[Symbol.iterator]` return `this` is what lets any iterator drop straight into `for...of`, spread, destructuring and `Array.from`. The convenience is real; the single-use semantics are the price. The same shape appears anywhere the language hands you a cursor: `set.values()`, `map.entries()`, `array.keys()`, and the object returned by calling a generator function. Everything a *collection* gives you is re-iterable; everything that is itself a *position in a sequence* is not. ## The failure modes in real code **Two passes over a parameter.** A function that computes a count and then formats the items works when callers pass arrays and silently returns nothing for the second pass when someone passes `map.values()`: ```js function report(items) { const n = [...items].length; // drains it const lines = [...items]; // [] for an iterator argument return { n, lines }; } ``` **Partial consumption.** `for...of` with a `break`, or destructuring that takes only the first element, stops mid-way. For an array the cursor is discarded and irrelevant; for an iterator the caller's object is left parked at that position, and whoever holds it next resumes from there rather than from the start. **Caching a converted value.** Storing `map.values()` in a module-level constant intending to reuse it is a bug in waiting: the first consumer wins and every later one sees an empty sequence. ## Writing code that survives both There are three defensible strategies, and the interview answer is knowing which you chose and why. 1. **Materialise at the boundary.** `const items = Array.from(input);` as the first line. One pass over whatever came in, and everything after it is an array with array semantics. Costs memory proportional to the sequence and is wrong for a deliberately unbounded source. 2. **Guarantee one pass.** Structure the function so it iterates exactly once — accumulate everything you need in a single loop. This keeps streaming behaviour intact and is the right choice for large sources. 3. **Accept a factory.** Take `() => Iterable<T>` instead of `Iterable<T>` when you genuinely need repeated passes over a lazily produced sequence. Calling it again produces a fresh iterable, and the contract is explicit in the signature. What you should *not* do is test with an array and assume the parameter type is honoured. "Takes any iterable" and "iterates it twice" is a contradiction that arrays hide. ## Detecting the distinction There is no reliable predicate for "is this re-iterable" — the protocols do not expose it. A rough heuristic is that if `x[Symbol.iterator]() === x`, you are holding an iterator and it is single-use: ```js const isIterator = (x) => typeof x?.next === 'function' && x[Symbol.iterator]?.() === x; ``` Use it for a defensive early conversion or an assertion in a library entry point, not as a general-purpose type test — a user-written iterable is free to return a stale cursor without matching this shape. The robust answer remains: decide your consumption contract, document it, and either materialise once or iterate once.
- Why is the empty second pass considered worse than an exception would be?Because nothing signals it. The loop body simply never runs, so a count comes back zero, a render is blank, or a reduce without an initial value throws far from the real cause. A TypeError at the point of re-iteration would have named the mistake immediately.
- How would you write a helper that accepts any iterable and iterates it twice safely?Convert first: `const items = Array.from(input)` as the opening line, then use `items` everywhere. It costs one pass and memory proportional to the input, and it makes the function's contract honest. For unbounded sources, restructure to a single pass instead, or take a factory that returns a fresh iterable per call.
- What happens to a Map iterator when a for...of over it breaks early?The loop stops and the iterator is left parked at that position, not reset. Anyone who holds the same object and iterates it next resumes after the element where the break happened — which is why passing a partially consumed iterator around is so error-prone.
- Is there a reliable way to test whether a value can be iterated twice?Not in general — the protocols expose no such flag. Checking that `x[Symbol.iterator]() === x` identifies the built-in iterator shape and is useful as a defensive guard, but a hand-written iterable can return a stale cursor without matching it. Design the contract instead of sniffing it.
An array is a book you can reopen at page one whenever you like; a Map iterator is a bookmark that only moves forward — once it reaches the back cover, reopening it shows you the back cover.
saying these in an interview costs you the question
- Says the second loop throws because the value is exhausted
- Claims Symbol.iterator always returns a fresh iterator
- Thinks map.values() returns an array
- Assumes any iterable can be looped repeatedly
- Caches a values() result expecting to reuse it