Iterators & Generators
MedThe iteration protocol defines how JavaScript objects become iterable — usable with for...of, spread, destructuring, and Array.from. Any object with a Symbol.iterator method that returns a next() function is iterable. Generator functions (function*) provide a concise way to create iterators using yield, enabling lazy evaluation and infinite sequences.
Interactive Visualization
for...in vs for...of
const obj = { a: 1, b: 2 };
const arr = [10, 20, 30];Key Points
- An iterator is an object with a next() method that returns { value, done }
- An iterable is an object with a [Symbol.iterator]() method returning an iterator
- Generator functions (function*) pause at each yield and resume when next() is called
- Generators are lazy — they compute values on demand, not all at once
- yield* delegates to another iterable or generator
- for...of, spread, destructuring, and Array.from all consume iterables
Code Examples
Custom Iterable Object
const range = { from: 1, to: 5, [Symbol.iterator]() { let current = this.from const last = this.to return { next() { if (current <= last) return { value: current++, done: false } return { value: undefined, done: true } }, } }, } for (const num of range) console.log(num) // 1, 2, 3, 4, 5 const arr = [...range] // [1, 2, 3, 4, 5]
The range object implements Symbol.iterator, making it work with for...of, spread, and destructuring.
Generator Function Basics
function* countdown(n) { while (n > 0) { yield n n-- } yield 'Go!' } const counter = countdown(3) console.log(counter.next()) // { value: 3, done: false } console.log(counter.next()) // { value: 2, done: false } console.log(counter.next()) // { value: 1, done: false } console.log(counter.next()) // { value: 'Go!', done: false } console.log(counter.next()) // { value: undefined, done: true }
function* creates a generator. Each yield pauses execution. Calling next() resumes from where it paused.
Infinite Sequences with Lazy Evaluation
function* fibonacci() { let a = 0, b = 1 while (true) { yield a ;[a, b] = [b, a + b] } } function take(n, iterable) { const result = [] for (const value of iterable) { result.push(value) if (result.length >= n) break } return result } console.log(take(8, fibonacci())) // [0, 1, 1, 2, 3, 5, 8, 13]
The fibonacci generator produces an infinite sequence but only computes values when requested.
yield* Delegation
function* inner() { yield 'a'; yield 'b' } function* outer() { yield 1 yield* inner() // delegates to inner yield 2 yield* [10, 20] // delegates to any iterable yield 3 } console.log([...outer()]) // [1, 'a', 'b', 2, 10, 20, 3] // Tree traversal function* traverse(node) { yield node.value if (node.left) yield* traverse(node.left) if (node.right) yield* traverse(node.right) }
yield* delegates to another iterable, flattening its values. Powerful for recursive data structures.
Common Mistakes
- Generators are single-use — once exhausted, next() always returns { done: true }
- Using return inside a generator — the value is skipped by for...of
- Not breaking out of infinite generator loops
- Confusing Symbol.iterator (makes iterable) with the iterator itself (has next())
Interview Tips
- Implement a custom iterable from scratch — Symbol.iterator and next() pattern
- Demonstrate lazy evaluation with infinite sequences and explain the memory advantage
- Generators conform to both iterator and iterable protocols
- Practical uses: paginated API consumption, tree traversal, streaming data