Event loop and iteration
JavaScript's single thread looks like a paradox: how does one thread serve timers, the network, clicks, and promises without freezing anything? The answer is the event loop: a mechanism that takes ready callbacks from queues and feeds them one at a time onto an empty stack. Understanding the order of these queues — microtasks versus macrotasks — separates someone who predicts a program's output from someone who guesses.
The second half of this topic is the iteration protocol: the single [Symbol.iterator] interface that for...of, spread, and destructuring rest on, and generators — functions that can pause and resume. They also sit conceptually beneath async/await. Reactive push streams round out the topic as an alternative model for working over time.
Topic map
- Event loop — one stack, the task queue, and the rule "run a task only after the stack empties".
- Micro/macrotasks — why promises beat timers and how fully draining the microtask queue starves macrotasks.
- Iterators and iterables — the
[Symbol.iterator]andnext()protocol that powersfor...ofand spread. - Generators —
function*that pauses atyieldand produces values lazily on demand. - Reactive streams — RxJS, hot and cold observables, backpressure, and pull versus push.
Common traps
| Mistake | Consequence |
|---|---|
| Thinking the event loop is multithreaded | False model — it is strictly single-threaded, callbacks run one at a time |
Thinking setTimeout(fn, 0) runs before Promise.then | Microtasks always drain before the next macrotask |
| Believing the loop takes one microtask per macrotask | It drains the entire microtask queue between macrotasks |
Iterating a plain object with for...of | It has no [Symbol.iterator] — a runtime error |
| Thinking calling a generator runs its body | It only returns a paused iterator |
Forgetting to unsubscribe from a hot observable | Leaks the subscription and its producer |
Interview relevance
Event-loop ordering is the classic senior "predict the output" question. The interviewer hands you a snippet with setTimeout, Promise.then, and synchronous console.log and watches whether you sort it into phases. Separately they ask what makes an object iterable and how a generator models an infinite sequence without infinite memory.
Typical checks:
- That there is one thread and callbacks run to completion, one at a time.
- That the whole microtask queue drains before the next macrotask.
- The iteration protocol and the difference between
for...of(values) andfor...in(keys). - That calling a generator returns an iterator and values are produced lazily.
Common wrong answer: "setTimeout(fn, 0) runs immediately and before promises." In fact the timer callback is a macrotask: it waits for both the stack to empty and the microtask queue to fully drain, so any Promise.then beats it.