How JavaScript Generators works with Examples

Today I’ve been reading about Redux-Saga. One of the key concepts there is the use of generator functions (function*). Generators are a JavaScript feature I’d heard of before, but I never really needed them in my day-to-day work—probably because I didn’t fully understand why or when to use them.
Today I finally understood their power, when to use them, and how they can help with common scenarios.
If we read the official documentation about generators on MDN, it says:
The
Generatorobject is returned by a generator function and it conforms to both the iterable protocol and the iterator protocol.
MDN also provides this example:
function* infinite() {
let index = 0;
while (true) {
yield index++;
}
}
const generator = infinite(); // "Generator { }"
console.log(generator.next().value); // 0
console.log(generator.next().value); // 1
console.log(generator.next().value); // 2
// …After reading that definition and example, a common way to describe a generator is:
A generator is a function whose execution you can pause and resume, using the yield keyword.
I shared this idea with my friend @Bezael Pérez , and he asked me:
“When do you actually need to pause or resume execution?”
That was a great question, so I decided to read more, investigate, and build a small example to make it clearer for both of us.
The Function vs Function*
In JavaScript, normal functions return a single value (or nothing). When you call them, they run from start to finish and then they’re done.
For example, imagine we have a getQuestion function that stores all the questions we want to ask a user. We pass an index as a parameter, with a default value:
function getQuestion(question = 0) {
const questions = ['What is your name', 'What is your email', 'What is your age'];
return questions[question];
}
console.log(getQuestion());
// Output:
// 'What is your name'This works perfectly. But if I need the next question, I have to call the function again with a different index:
console.log(getQuestion(2))
'What is your age'In this example, the caller (the “client”) is responsible for knowing the state, and must pass the correct index every time. In a real app, the client might be a UI component.
It would be nice if we could make this easier for the client and encapsulate that “question flow” logic somewhere else.
The Generator
A generator function uses function* together with yield to return multiple values over time. This makes it easy to turn data into an iterable sequence or a data stream. Every time the generator hits yield, it pauses execution and returns that value.
function* register(){
yield "Hello";
name = yield "World";
return `Hello ${name} from Generator`
}If we call register(), we might expect it to run like a normal function, but instead it returns a generator object. That object is responsible for controlling the execution.
const registerFlow = register();The generator object provides a .next() method, which moves execution to the next yield and returns an object with { value, done }.
Here’s a more complete example:
function* gretting() {
yield "Hi from generator";
const name = yield "World";
const lastname = yield "Hello";
return `Hello ${name} ${lastname} from generator`
}
const grettingFlow = gretting();
console.log(grettingFlow.next());
//prints Hi from generator
grettingFlow.next("dany");
grettingFlow.next("paredes");Each call to .next() returns an object like this:
{ value: 'World', done: false }
{ value: 'Hello', done: false }
{
value: 'Hello dany paredes from generator',
done: true
}-
value is the current yield value (or the final return value).
-
done is false while the generator still has steps to execute.
-
When the generator finishes (return), done becomes true.
Now that we have an intuition for generators, let’s play with a real-world use case.
Build a Form Stepper with Generator
I’m using TypeScript + React inside a Next.js app. This example lives in a Client Component in the Next.js App Router (for example, app/page.tsx).
Generator<Y, R, N>Where:
-
Y = the type of values yielded by the generator
-
R = the return type when the generator finishes
-
N = the type of values passed into .next(value)
Our generator is registerFlow() looks like:
export function* registerFlow(): Generator<
string,
{ name: string; age: string; email: string },
string
> {this means:
-
The generator will yield strings (the questions).
-
The final return value is an object with name, age and email.
-
Each call to .next(...) will pass a string as the answer.
Here’s the full generator:
export function* registerFlow(): Generator<
string,
{ name: string; age: string; email: string },
string
> {
const name = yield "What is your name?";
const age = yield "What is your age ?";
const email = yield "What is your email?";
return {
name,
age,
email,
};
}The generator is responsible for:
-
Sending questions to the UI.
-
Receiving answers from the UI.
-
Returning the final registration data.
Using RegisterFlow with NextJS
In this section, I’ll show how to plug the generator into a Next.js Client Component to build a tiny form stepper like this.

First, we create a ref using useRef hook with the generator:
const formStepRef = useRef(registerFlow());Then we set up our component state:
const formStepRef = useRef(registerFlow());
const [question, setQuestion] = useState<string | undefined>(undefined);
const [answer, setAnswer] = useState("");
const [registrationData, setRegistrationData] = useState<
Registration | undefined
>(undefined);On mount, we move to the first step using useEffect hook:
useEffect(() => {
const initialStep = formStepRef.current.next();
if (initialStep.done) {
setRegistrationData(initialStep.value);
return;
}
setQuestion(initialStep.value);
}, []);Then we handle step changes using .next and done:
const changeStep = () => {
const steps = formStepRef.current.next(answer);
if (steps.done) {
setRegistrationData(steps.value);
return;
}
setQuestion(steps.value as string);
setAnswer("");
};The final code:
"use client";
import { useEffect, useRef, useState } from "react";
import { registerFlow } from "./registerflow";
import { clsx } from "clsx";
export type Registration = {
name: string;
age: string;
email: string;
};
const Page = () => {
const formStepRef = useRef(registerFlow());
const [question, setQuestion] = useState<string | undefined>(undefined);
const [answer, setAnswer] = useState("");
const [registrationData, setRegistrationData] = useState<
Registration | undefined
>(undefined);
useEffect(() => {
const initialStep = formStepRef.current.next();
if (initialStep.done) {
setRegistrationData(initialStep.value);
return;
}
setQuestion(initialStep.value);
}, []);
const changeStep = () => {
const steps = formStepRef.current.next(answer);
if (steps.done) {
setRegistrationData(steps.value);
return;
}
setQuestion(steps.value as string);
setAnswer("");
};
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-100">
<div className="bg-white p-8 rounded-xl shadow-md flex flex-col gap-6 w-full max-w-md">
<h1 className="text-3xl font-bold text-gray-800 text-center mb-2">
Registration Page
</h1>
<h3 className="text-lg text-gray-600 text-center min-h-[2em]">
{registrationData ? `Thanks ${registrationData.name}!` : question}
</h3>
{!registrationData && (
<input
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded focus:outline-none focus:ring-2 focus:ring-blue-400 px-4 py-2 w-full transition"
value={answer}
onChange={(e) => setAnswer(e.target.value)}
placeholder="Enter your answer"
/>
)}
<button
onClick={() => changeStep()}
className={clsx(
`mt-4 w-full py-2 px-4 bg-blue-600 text-white text-lg font-semibold rounded hover:bg-blue-700 transition disabled:opacity-50`,
{ invisible: registrationData }
)}
>
{registrationData ? "Finish" : "Next"}
</button>
</div>
</div>
);
};Conclusion
I want to confess that Generators can look a bit strange the first time you see function* and yield, but the mental model is simple:
-
A generator lets you pause and resume a function.
-
Each yield is like a “checkpoint” where you can send a value out and later send a value back in.
-
The .next() calls drive the flow from the outside.
I used a generator to model a multi-step form with a Next.js app, but the same idea applies to more complex flows: wizards, onboarding, async workflows, and of course, for example, the Redux-Saga library was what triggered my interest to learn about them , for example doing a small experiment
Once you’re comfortable with simple patterns like this form stepper, reading Redux-Saga code becomes much easier
- Source code: https://github.com/danywalls/wizard-step
Real Software. Real Lessons.
I share the lessons I learned the hard way, so you can either avoid them or be ready when they happen.
No spam ever. Unsubscribe at any time.