You will be able to write JavaScript generator functions that pause and resume to produce values lazily, and use them with iterators, for...of, and spread.
What it is
A generator is a special function declared with function*. When called, it does not run immediately; it returns a generator object that follows the iterator protocol. Each call to next() runs the function until the next yield, returns the yielded value, and pauses while keeping local variables and execution position. Related terms: iterator, iterable, yield, next(), return(), and throw().
Why it matters
- Creates lazy sequences, so values are produced only when needed.
- Useful for infinite or very large data streams without storing everything in memory.
- Enables custom iteration over objects, such as tree nodes or paginated API results.
- Works naturally with
for...of, spread syntax, and destructuring. - Provides a simple state machine pattern for step-by-step algorithms.
Syntax or steps
- Declare a generator with
function*. - Use
yieldto pause and emit a value. - Call the function to get an iterator object.
- Use
next()to resume and get{ value, done }. - Consume it with
for...ofor spread when you want all values.
Example
function* counter(start = 1, end = 3) {
let current = start;
while (current <= end) {
yield current;
current += 1;
}
}
const iterator = counter(1, 3);
console.log(iterator.next());
console.log(iterator.next());
console.log([...counter(1, 3)]);
Part by part:
function* counterdeclares a generator, not a normal function.yield currentpauses after producing a value and rememberscurrent.counter(1, 3)returns an iterator; the body has not finished yet.iterator.next()resumes until the next yield and returns an object withvalueanddone.[...counter(1, 3)]consumes the generator and collects all yielded values into an array.
Common mistakes
- Forgetting the star:
function gen()returns a normal function, not a generator. - Using
yieldoutside a generator function; it is only valid insidefunction*. - Calling the generator and expecting an array immediately; you must iterate or call
next(). - Ignoring
donewhen usingnext()manually, which can lead to readingundefinedafter the sequence ends.
When to use it
Use generators when you need lazy, stateful iteration. Use arrays when the full data set is small and already available.
| Approach | Best for | Trade-off |
|---|---|---|
| Generator | Large, infinite, or computed-on-demand sequences | More syntax and manual control |
| Array | Small fixed collections | Stores all values in memory |
| Custom iterator object | Complex reusable iteration logic | More boilerplate than function* |
Practice
Guided exercise: write a generator named squares that yields the squares of numbers from 1 to n.
function* squares(n) {
for (let i = 1; i <= n; i += 1) {
yield i * i;
}
}
console.log([...squares(4)]);
Expected output: [1, 4, 9, 16].
Challenge: create an infinite generator that yields Fibonacci numbers, then use next() to get the first five values. Hint: keep two variables, a and b, and yield a before updating them.
Quick check
What does next() return when a generator has finished?
It returns an object like { value: undefined, done: true }, unless the generator used return with a final value.
Summary
Generators turn a function into a resumable sequence producer. They are most useful when values should be computed lazily, one at a time, while preserving local state between steps.