Topics
v6.0.0
On this page · sections

JavaScript Collections, Iterables & Generators

Can you pick Map over an object, implement for...of, and pause a workflow with yield?

Objects are not a general dictionary. Iteration is a protocol, not a for loop. Generators are functions that return iterators.

These questions test:

  • Map / Set vs object / array
  • WeakMap and GC
  • Symbol.iterator
  • yield and lazy sequences
  • When a generator is the wrong API

Questions

Collections

  1. Map vs Object, Set vs Array
  2. WeakMap and WeakSet

Iteration

  1. The Iterator Protocol
  2. Generators and yield

Senior-Level Follow-ups

  1. Why Object.keys Misses Map
  2. return(), throw(), and Cleanup
  3. Async Iteration

Collections

1. Map vs Object, Set vs Array

Why This Matters

Object keys are strings or symbols. Map keys are any value, including objects.

Problem

When do you use Map/Set instead of objects and arrays?

Example

const obj = {}; obj[1] = "a"; obj["1"] = "b"; Object.keys(obj); // ["1"] — one key
const map = new Map(); map.set(1, "a"); map.set("1", "b"); map.size; // 2

Map preserves insertion order, has .size, and does not inherit Object.prototype.

Set is unique values with SameValueZero (NaN matches NaN). Membership is O(1) typical; array.includes is O(n).

Use an object for JSON-shaped records and known string keys. Use Map for dictionaries, caches keyed by object, or mixed key types.

Interview Answer

“I use Map when keys are not plain strings, when I need size/order without prototype keys, or when I mutate membership often. I use Set for uniqueness and membership. I use objects for records.”

2. WeakMap and WeakSet

Why This Matters

Caches that key on DOM nodes or objects must not keep those objects alive forever.

Problem

What can be a WeakMap key, and what cannot you iterate?

Example

const meta = new WeakMap(); function tag(el, info) { meta.set(el, info); }

When el is removed and unreachable, the entry can be collected.

Keys must be objects (or non-registered symbols in WeakMap — practically: objects). No .size, no iteration, no .clear on WeakSet/WeakMap in the usual API.

That missing iteration is the point: the engine must be free to drop entries.

Interview Answer

“WeakMap holds a non-owning key. It is for metadata and caches whose lifetime should follow the object. I cannot enumerate it, so it is not a general Map. If I need iteration, I use Map and accept the retention.”

Iteration

3. The Iterator Protocol

Why This Matters

for...of, spread, and destructuring all call obj[Symbol.iterator]().

Problem

What must an iterable return, and how is that different from an iterator?

Example

const iterable = { [Symbol.iterator]() { let n = 0; return { next() { if (n >= 3) return { done: true, value: undefined }; return { done: false, value: n++ }; }, }; }, }; [...iterable]; // [0, 1, 2]
Iterable → has Symbol.iterator → returns an iterator Iterator → has next() → { value, done }

Arrays, Maps, Sets, strings, and arguments are iterable. Plain objects are not.

A well-known Symbol is how JS adds protocols without colliding with user keys: Symbol.iterator, Symbol.asyncIterator, Symbol.toStringTag, Symbol.toPrimitive.

Interview Answer

“An iterable exposes Symbol.iterator, which returns an iterator with next(). for...of is that protocol. I implement it on custom collections instead of inventing .each().”

4. Generators and yield

Why This Matters

A generator is iterator sugar plus pause/resume.

Problem

What does function* return, and when does the body run?

Example

function* range(max) { for (let i = 0; i < max; i++) { yield i; } } const it = range(3); it.next(); // { value: 0, done: false } [...range(3)]; // [0, 1, 2]

The body starts on the first next(). Each yield pauses and hands a value out.

function* twoWay() { const x = yield 1; yield x + 1; } const g = twoWay(); g.next(); // { value: 1 } g.next(10); // { value: 11 } — 10 becomes the result of yield

Use generators for lazy sequences, pagination, and custom iterables. Do not use them as a hidden state machine if a plain object would be clearer.

Interview Answer

“A generator function returns an iterator. yield pauses and produces a value. The next next(arg) resumes and supplies arg as the result of yield. It is lazy: nothing runs until next().”

Senior-Level Follow-ups

5. Why Object.keys Misses Map

Object.keys(new Map([["a", 1]])); // []

Map data is internal. JSON.stringify also produces {}.

Clone and equality must handle Map/Set explicitly — Deep Clone & Equality.

6. return(), throw(), and Cleanup

function* withFinally() { try { yield 1; yield 2; } finally { console.log("cleanup"); } } const g = withFinally(); g.next(); g.return(); // cleanup

for...of calls iterator.return() on break. That is why try/finally inside a generator is a real resource hook.

7. Async Iteration

for await (const chunk of readable) { consume(chunk); }

This calls obj[Symbol.asyncIterator](). next() returns a Promise of { value, done }.

async function* yields Promises. Use it for paginated APIs. Prefer streams when backpressure matters.

Final Interview Strategy

1. Map for dictionaries; object for records 2. Set for membership; array for sequences 3. WeakMap when the key should not pin memory 4. for...of is Symbol.iterator 5. Generators implement iterators lazily 6. Map is invisible to Object.keys 7. return() runs finally on early exit

Senior-level signal: Choose the collection by key identity and lifetime, then implement iteration as a protocol — not as a custom forEach you invented.

Last updated on