cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Generators

Learn JavaScript generator functions: how function* and yield pause work, why generators are iterators, how yield* delegates, how return and throw finish or interrupt, and how infinite lazy sequences stay safe.

By the end you can
  • 01
    Pause and resume a functionTrace exactly when a generator body runs and what each yield returns.
  • 02
    Use generators as iteratorsFeed a generator object to for...of, spread, and helper generators safely.
  • 03
    Control and compose flowsUse return(), throw(), yield*, and lazy sequences without surprises.

Functions that can pause

A regular JavaScript function is like a sprint: call it, and it runs until it returns or throws. A generator function is different. It can run for a while, yield a value, pause with all its local variables still in place, and continue later when someone calls next().

Generators sit right on top of the iterator protocol from the previous lesson. A generator object has a next() method that returns { value, done }, and it is also iterable, so it works with for...of, spread, and destructuring. The difference is that you write the iterator as ordinary-looking control flow instead of hand-building a next method.

Definition

A generator is a function declared with function* whose body can pause at yield and resume later through the generator object’s next, return, or throw methods.

Real-life analogyA generator is a storyteller who pauses

Imagine a storyteller who pauses at the end of every scene. They do not start over when you say “go on.” They remember the characters, the plot, and the exact place in the sentence. That is the generator mental model.

In real life: The storyteller starts a tale
In JavaScript: The first next() starts the generator body
In real life: A pause after each scene
In JavaScript: Each yield returns a value and suspends the function
In real life: The storyteller remembers the exact sentence
In JavaScript: Local variables and the source position are kept
In real life: You say “go on”
In JavaScript: Calling next() resumes from the paused yield

Where the analogy stops: A person can improvise between pauses. A generator resumes exactly where JavaScript suspended it, following the same code path unless return() or throw() interrupts.

In this lesson you will step through a real recorded run, command a live generator with buttons, delegate to another generator with yield*, and safely take values from sequences that could go on forever.

function* and yield

STEP THROUGH

The syntax is small: put a star after function, then use yield inside the body. The behavior is the big idea: calling the generator function runs none of the body. It only returns a generator object. The first next() call starts the body and runs until the first yield.

1. Createconst tale = story();

No line inside story has run yet.

2. Resumetale.next()

Run until the next yield, return, or throw.

3. Suspend{ value, done: false }

The function is paused with its variables intact.

Step through the replay. It was recorded from real JavaScript calls, but it is not an engine debugger. The “Running in” row shows whether we are back in the script or currently inside the generator context. Change the last command to see normal resume, early return, and a thrown problem.

Step through pause, resume, and cleanup
Step 0 of 10Ready
Your turn: follow the blue line

Predict first: what runs when story() is called? Step to see that nothing inside the body runs until next() resumes it.

Running in
  1. script
Next: line 17
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
function* story() {  console.log("story starts");  try {    yield "chapter 1";    console.log("after first pause");    yield "chapter 2";    console.log("after second pause");    return "the end";  } catch (error) {    console.log("caught: " + error.message);    return "recovered";  } finally {    console.log("cleanup");  }} console.log("created");tale.next();tale.next();
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Change the last command on line 20

Changing the command starts a fresh recording. Watch the Running in row switch between script and the suspended generator.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Two details matter for professional code. First, the argument passed to the first next(value) is ignored because no paused yield exists yet to receive it. Sending values back into generators is powerful, but this course saves it for Lesson 31.5. Second, a generator’s local variables are not global state. They live inside that one suspended generator object, similar to the local execution context you saw in the Execution contexts lesson.

Generators as iterators

INTERACTIVE

A generator object is both an iterator and an iterable. Iterator means it has next(). Iterable means it has Symbol.iterator and can be consumed by for...of. For generator objects, iterator[Symbol.iterator]() === iterator is true.

Generator object vs array
QuestionGenerator objectArray
When are values computed?On demand, when a consumer asksUsually already stored
Can it be infinite?Yes, if the consumer stopsNo practical infinite array
Can you loop it twice?Not the same object after it is consumedYes, arrays keep their elements
ProtocolIterator and iterableIterable, and array methods like map
Small generator as an iteratorPop out in the code editor (opens in a new tab)JavaScript
function* colors() {  yield "red";  yield "blue";} const list = colors();console.log(list.next());       // { value: "red", done: false }console.log([...colors()]);     // ["red", "blue"]console.log([...list]);         // ["blue"] because red was already consumed

That last line surprises many people. list is the same iterator object you already advanced once, so spreading it starts from its current pause, not from the beginning. If you need a fresh pass, call the generator function again.

Send commands to one live generator
Generator under testPop out in the code editor (opens in a new tab)JavaScript
function* task() {  console.log("start");  try {    yield "first";    console.log("after first");    yield "second";  } catch (error) {    console.log("caught " + error.message);  } finally {    console.log("cleanup");  }  return "done";}
Results and internal loglive

Returned results

No commands yet.

Inside the body

No body logs yet.

Try it yourself

The generator is created but untouched. Press next() to start the body.

A live generator object. Reset creates a new one; buttons keep talking to the current one.

next(), return(), and throw()

next() is the ordinary “go on” command. Two other methods let the caller close or interrupt a paused generator:

  • return(value) asks the generator to finish. It returns { value, done: true } and runs pending finally blocks. A yield inside finally can pause once more before completion.
  • throw(error) throws at the paused yield. A try...catch inside the generator can handle it. If the generator has never started, the throw happens immediately.

These methods make generators good citizens around cleanup. They work well with the try, catch & finally rules you already know: cleanup belongs in finally, even when iteration stops early.

Arrow functions cannot be generators

JavaScript has function* declarations and expressions, but no generator arrow syntax. Also, generator functions are not constructors: new story() throws a TypeError.

yield* delegation

INTERACTIVE

yield* means “yield everything from this iterable before continuing here.” It works with arrays, strings, sets, custom iterables, and other generators. When the delegate is a generator, its final return value becomes the value of the yield* expression.

Real-life analogyyield* hands the microphone to another storyteller

A host can tell part of a story, hand the microphone to a guest, and wait until the guest is done. Only then does the host continue, now knowing the guest’s final private note.

In real life: The host starts the show
In JavaScript: The outer generator yields its own values
In real life: The guest takes the microphone
In JavaScript: yield* guest() delegates each yielded value
In real life: The guest’s final line
In JavaScript: The delegate’s return value
In real life: The host takes the mic back
In JavaScript: Execution resumes after yield*

Where the analogy stops: The audience hears the guest’s yielded scenes, not the guest’s final return line unless the host chooses to yield that value afterward.

Hand the microphone to another iterable
Delegating generatorPop out in the code editor (opens in a new tab)JavaScript
function* leaf(name) {  yield name + ": start";  yield name + ": end";  return name + " finished";} function* storyTeam() {  yield "host intro";  const finalLine = yield* leaf("guest");  yield "host heard " + finalLine;  return "host finished";} console.log([...storyTeam()].join(" | "));
Outputdelegated
  • host intro
  • guest: start
  • guest: end
  • host heard guest finished
  • Final next() result: { value: "host finished", done: true }
Try it yourself

yield* leaf(guest) hands the microphone to the guest generator. When the guest returns, the host hears that final line as finalLine.

yield* works with any iterable. A generator delegate can also return one final value to the delegating generator.

The recursive flatten example is the same idea as the Recursion lesson: each nested array gets its own call, and yield* passes values from the deeper call to the outside consumer.

Infinite sequences

INTERACTIVE

Because generators are lazy, they can describe sequences that would be impossible to store in an array: IDs, timestamps, retry attempts, Fibonacci numbers, pages from a long search, or events from a stream. The rule is simple: an infinite generator is safe only when the consumer stops.

Take a small bite from an endless sequence
Lazy helpersPop out in the code editor (opens in a new tab)JavaScript
function* ids() {  let id = 1;  while (true) {    console.log("compute id " + id);    yield id;    id += 1;  }} function* take(iterable, limit) {  const iterator = iterable[Symbol.iterator]();  for (let count = 0; count < limit; count += 1) {    const result = iterator.next();    if (result.done) return;    yield result.value;  }} console.log([...take(ids(), 3)].join(", "));
IDscomputed on demand

Values: 1, 2, 3

Log:

  1. compute id 1
  2. compute id 2
  3. compute id 3
Try it yourself

take(ids(), 3) computes exactly three IDs.

The lesson deliberately never runs [...ids()] or [...fibonacci()]; those would never finish.
Never spread an endless generator

[...ids()], Array.from(ids()), and a for...of loop with no break keep asking for values forever. Use a limiter such as take(ids(), 3).

Lazy, eager, or unsafe?
  • [...take(ids(), 3)]
  • orders.map(order => order.id)
  • [...ids()]
  • function* paidOrderIds(orders) { ... }
  • orders.filter(order => order.paid)
  • Array.from(fibonacci())
Try it yourself
0 of 6 correct

Sort each expression by how it consumes values. The warnings are practical: never ask an infinite source for all values.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Where you’ll use generators

Generators are not for every loop. Reach for them when a process naturally produces a sequence over time, when building the whole array would waste memory, or when recursion needs to stream results upward.

A realistic lazy filterPop out in the code editor (opens in a new tab)JavaScript
const orders = [  { id: 1, paid: true },  { id: 2, paid: false },  { id: 3, paid: true },]; function* paidOrderIds(items) {  for (const order of items) {    if (order.paid) yield order.id;  }} console.log([...paidOrderIds(orders)].join(", "));

This example is small, but the shape scales. An eager array solution such as orders.filter(...).map(...) is perfectly fine for a short in-memory array. A generator version can be useful when orders arrive one page at a time or when later code may stop after the first match.

  • Tokenizing text: yield one token at a time.
  • Walking trees: yield each file, folder, or menu item as you find it.
  • Generating test data: yield as many cases as the test needs.
  • Lazy pipelines: combine filter, map, and take without intermediate arrays.

Misconceptions and edge cases

“Calling a generator runs it.”

Calling returns a generator object. The body waits until the first next().

“A generator object is a reusable array.”

It is a one-position-at-a-time iterator. Once consumed, it stays done. Call the generator function again for a fresh iterator.

“return() skips cleanup.”

It runs pending finally blocks. That is why generators can clean up when a loop exits early.

“yield* only works with generators.”

It works with any iterable. The special part with generator delegates is that yield* evaluates to their final return value.

“All lazy code is safer.”

Laziness saves work only if the consumer stops. Spreading an infinite generator is a bug, not an optimization.

Commands sent to a generator
CommandWhere it resumesTypical result
next()At the start or after the paused yieldNext yielded value, or final { done: true }
return(value)At the paused yield, as an early close requestUsually { value, done: true }, after finally
throw(error)At the paused yield, as if that yield threwCaught inside, or rethrown to the caller

Practice: generators

5 EXERCISES
Exercise 1 · Warm-upPredict two yields and the final done

Write the three logged values separated by commas.

Starter codePop out in the code editor (opens in a new tab)JavaScript
function* letters() {
  yield "A";
  yield "B";
}

const it = letters();
console.log(it.next().value);
console.log(it.next().done);
console.log(it.next().done);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeWrite an odds generator

    Create a generator that yields odd numbers up to the limit.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    function* odds(limit) {
      // yield 1, 3, 5 up to limit
    }
    
    console.log([...odds(5)].join(","));

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFind the infinite bug

      What is wrong with this code, and how would you limit it safely?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      function* ids() {
        let id = 1;
        while (true) yield id++;
      }
      
      console.log([...ids()].slice(0, 3));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeCapture a yield* return value

        Predict the output of the delegated program.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        function* one() {
          yield "x";
          return "done";
        }
        function* two() {
          const message = yield* one();
          yield message;
        }
        console.log([...two()].join("|"));

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeUse a generator for a real task

          Write a generator that receives task objects like { title: "Fix nav", tags: ["ui"] } and yields only the titles whose tags include a requested tag.

            Quiz: check your understanding

            7 QUESTIONS

            Pick an answer, then read every explanation. The wrong choices name common generator mistakes.

            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7When does a generator function body start running?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does this generator sequence print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function* colors() {
                yield "red";
                yield "blue";
              }
              const list = colors();
              console.log(list.next().value);
              console.log(list.next().done);
              console.log(list.next().done);

              Choose an answer to see the explanation.

            3. Question 3 of 7Which statement about generator objects is true?

              Choose an answer to see the explanation.

            4. Question 4 of 7What does return("stop") do to a paused generator?

              Choose an answer to see the explanation.

            5. Question 5 of 7What does yield* otherIterable do?

              Choose an answer to see the explanation.

            6. Question 6 of 7What does this yield* program print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function* inner() {
                yield "a";
                return "done";
              }
              function* outer() {
                const final = yield* inner();
                yield final;
              }
              console.log([...outer()].join(","));

              Choose an answer to see the explanation.

            7. Question 7 of 7Which line should you never run with an infinite generator?

              Choose an answer to see the explanation.

            Key takeaways

            • function* creates generator functions; calling one returns a generator object without running the body.
            • Each next() resumes until the next yield, return, or thrown error.
            • Generator objects are both iterators and iterables, so they work with for...of and spread.
            • return() closes early and runs cleanup; throw() throws at the paused yield.
            • yield* delegates to another iterable and can receive a generator delegate’s final return value.
            • Infinite generators are safe only with a stopping consumer such as take.

            Remember the one-liner.
            A generator is a pausable function that produces iterator results on demand.

            Up next: Iterator helpers, lazy map, filter, take, and friends for any iterator.

            CompleteFrontend Clear concepts. Working examples.