Detailed explanation
Iterators and generators let JavaScript produce values one at a time instead of requiring a complete collection upfront. An iterator is an object with a next() method that returns an object containing value and done. When done is false, value is the next item; when done is true, iteration has finished. This pull-based model supports lazy sequences, custom traversal, and controlled processing of large datasets. Laziness avoids allocating all results upfront, but does not automatically make processing asynchronous or faster.
An iterable is different from an iterator: it exposes a [Symbol.iterator]() method that returns an iterator. Language features such as for...of, array spread, and array destructuring consume iterables through this method. An object with only next() is therefore not necessarily usable in for...of. Arrays and strings are iterable, while ordinary objects are not iterable by default. A reusable iterable can create a fresh iterator for each traversal; an iterator usually represents one traversal's current position.
A generator function, declared using function*, creates a generator object without immediately running its body. This object is both an iterator and an iterable. Calling next() starts or resumes execution until the next yield, return, or uncaught exception. At yield, the generator suspends while preserving local variables and its execution position. In function* ids() { let i = 1; while (true) yield i++; }, successive calls to g.next(), after const g = ids(), return { value: 1, done: false } and { value: 2, done: false }. The infinite sequence remains practical because values are computed only when requested.
Generators also support communication and cleanup. A later next(input) supplies the result of the suspended yield expression; the first next() argument is ignored because no yield is waiting yet. A return statement produces a final result with done: true, which for...of does not include as an item. yield* delegates to another iterable. Consumers can request termination using return(), allowing finally blocks to run. Interviewers typically check whether you distinguish the two protocols, explain suspension accurately, and avoid treating generators as promises or background execution.
Key points to remember
- An iterator exposes next(), returning an iteration result with value and done.
- An iterable exposes [Symbol.iterator](), which returns an iterator.
- Calling a function* creates a generator object; execution starts on next().
- yield suspends execution while retaining state, enabling lazy value production.
- Generator objects are iterable iterators; async generators use a separate asynchronous protocol.
Common mistakes candidates make
- Assuming every object with next() automatically works with for...of.
- Spreading an infinite generator into an array, which attempts to consume it without end.
- Claiming that yield runs code asynchronously or that a completed generator can restart.
Likely follow-up questions
Can a generator be iterated more than once?
A generator object retains its position and does not restart after completion. Call the generator function again to create a fresh object for another traversal.
What happens when you break out of a for...of loop over a generator?
The loop invokes the iterator's return() method if it exists, requesting closure. For a generator, this normally terminates execution and runs applicable finally blocks, making them useful for cleanup.
How do async generators differ from regular generators?
An async function* creates an async generator whose next() returns a promise for an iteration result. It supports await within its body and is typically consumed using for await...of.
Interview tip: Define iterator and iterable separately, trace two next() calls in the ids example, and finish with laziness as the practical benefit.
