Generators (function*, yield)
In the last lesson you wrote Symbol.iterator and next() by hand. Generators let you produce the exact same iterators with a fraction of the code — and a superpower normal functions don't have: the ability to pause partway through and resume later.
Learn Generators (function*, yield) in our free JavaScript course — a beginner-friendly interactive lesson with runnable examples, a practice exercise and a…
Part of the free JavaScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
That single feature unlocks infinite sequences, lazy pipelines, and clean state machines.
🎬 Real-World Analogy: A generator is like a TV series you stream:
You declare a generator with function* . Calling it does not run the body — it hands you back a generator object (which is both an iterator and an iterable). The body advances one yield at a time as you call next() .
Look closely: "Generator started" prints only after the first next() , not when we called greetings() . That delayed, on-demand execution is the heart of generators.
Because a generator object already implements the iteration protocol, every iterable-consuming feature works on it with zero extra code. Compare this to the hand-rolled range from the previous lesson:
That entire range is now four lines instead of fifteen. The generator handled all the next() / done bookkeeping for us.
A generator can loop forever because it only produces a value when asked. The consumer decides when to stop. Here is an unbounded id generator and an infinite Fibonacci sequence:
⚠️ Never do [...idMaker()] on an infinite generator — spread tries to drain it completely and your tab will hang. Always bound infinite generators with a counter or a break .
Generators aren't just one-way value pumps. A yield expression also receives the argument you pass to the next next(value) call. This makes generators usable as tiny, resumable conversations:
The value passed to next("Ada") becomes the result of the paused yield , so name ends up as "Ada" . The very first next() takes no argument because there is no paused yield waiting to receive it yet.
yield* delegates to another iterable, splicing all of its values into the current sequence. It is how you build big generators out of small ones:
These lines define a generator and collect its first three values, but they are scrambled. Reorder them so the program logs [ 1, 2, 3 ] .
Why: the generator must be fully declared (B–E) before you can call it (F). The let n = 1 (C) and the infinite while (D) belong inside the function body. Creating the empty array (A) can happen any time before the loop fills it (G), and the log (H) comes last. The infinite loop is safe because c.next() is only called three times.
created then A . Calling g() runs nothing, so "created" prints first. The first next() runs the body up to the yield 1 , printing "A" . "B" never prints because we never call next() a second time.
[ 10, 20 ] — spreading a generator drains it completely, collecting every yielded value into an array. Safe here because the generator is finite.
It logs got hello . The first next() pauses at yield "ready" . The second call next("hello") resumes the generator, and its argument becomes the value of that yield expression, so x === "hello" .
Up next: Strings & String Methods — the workhorse type behind almost every program! 🔤
Practice quiz
How do you declare a generator function?
- generator function name()
- function name*()
- function* name()
- async function name()
Answer: function* name(). The asterisk after the function keyword (function*) marks the function as a generator.
What happens when you call a generator function?
- It returns a generator object without running the body
- The body runs to completion immediately
- It throws unless you use await
- It runs up to the first return
Answer: It returns a generator object without running the body. Calling a generator only creates the generator object; the body runs lazily on each next() call.
How is yield different from return?
- They are identical
- yield ends the generator; return pauses it
- yield only works inside loops
- yield pauses and remembers position; return ends the generator
Answer: yield pauses and remembers position; return ends the generator. yield produces a value and pauses, preserving state; return finishes the generator for good.
What is logged? function* nums(){ yield 10; yield 20; } console.log([...nums()]);
Spreading a finite generator drains it, collecting every yielded value into an array.
What does yield* do?
- Delegates to another iterable, splicing in all its values
- Yields a Promise
- Ends the generator
- Yields undefined
Answer: Delegates to another iterable, splicing in all its values. yield* delegates to another iterable or generator, yielding all of its values in place.
Why is it dangerous to write [...idMaker()] on an infinite generator?
- It returns an empty array
- It throws a SyntaxError
- Spread tries to drain it completely and never finishes
- It only takes the first value
Answer: Spread tries to drain it completely and never finishes. Spread tries to consume every value, so an infinite generator freezes the page. Bound it with a counter or break.
In a chat generator, what does next('Ada') do for const name = yield 'question';?
- Ignores the argument
- Makes 'Ada' the value of the paused yield, so name becomes 'Ada'
- Throws because yield takes no value
- Restarts the generator
Answer: Makes 'Ada' the value of the paused yield, so name becomes 'Ada'. The argument to next() becomes the result of the paused yield expression.
Why is the argument to the very first next() call ignored?
- A bug in V8
- It is reserved for the return value
- Generators never accept arguments
- No yield is paused yet to receive it
Answer: No yield is paused yet to receive it. There is no paused yield waiting to receive a value on the first next(), so its argument is discarded.
What does the first 8 values of an infinite Fibonacci generator yield (starting at 0, 1)?
Starting from 0 and 1, each value is the sum of the previous two: 0,1,1,2,3,5,8,13.
Putting a yield inside a plain (non-generator) function causes what?
- It runs normally
- It silently returns undefined
- A syntax error — it must be function*
- It becomes async
Answer: A syntax error — it must be function*. yield is only valid inside a generator; forgetting the asterisk is a syntax error.