What are the iterable and iterator protocols? How do generators implement them, and what problems do they solve?
Understanding lazy sequences, custom iteration, and how generators pause/resume execution.
An object is an iterator if it has a next() method returning { value, done }. It is iterable if it has a [Symbol.iterator]() method returning such an iterator — that is what for...of, spread, and destructuring consume. Generators (function*) are the ergonomic way to produce both: calling one returns a generator object that is simultaneously an iterator AND iterable, and each yield pauses execution, handing a value out and suspending the function's entire stack frame until next() resumes it. This lazy, pull-based evaluation is the payoff: you can model infinite sequences (an ID generator), stream large or expensive data without materializing it all, and write stateful iteration as straight-line code instead of a hand-rolled state machine. yield* delegates to another iterable. Generators also accept values back in via next(value) (two-way communication) and were the mechanism async/await was originally built on. async generators (async function*) + for-await-of extend this to asynchronous streams like paginated APIs.
Lazy/infinite sequences, custom for...of over your own data structures, streaming pagination, and coroutine-style control flow.
Lazy: O(1) memory per step regardless of sequence length.