JavaScript Iterators and Generators
Iterators and generators are advanced JavaScript features that provide powerful, memory-efficient ways to work with sequences of data — especially large or potentially infinite ones. Understanding them deepens the knowledge of how JavaScript loops, spread operators, and async sequences truly work.
What is an Iterator?
An iterator is any object that implements a next() method. Each call to next() returns an object with two properties:
value— the current valuedone— a boolean;truewhen the sequence is exhausted
function makeIterator(items) {
let index = 0;
return {
next() {
if (index < items.length) {
return { value: items[index++], done: false };
} else {
return { value: undefined, done: true };
}
}
};
}
let iterator = makeIterator(["Apple", "Banana", "Mango"]);
console.log(iterator.next()); // { value: "Apple", done: false }
console.log(iterator.next()); // { value: "Banana", done: false }
console.log(iterator.next()); // { value: "Mango", done: false }
console.log(iterator.next()); // { value: undefined, done: true }Iterable Protocol
An iterable is an object that has a special method [Symbol.iterator]() which returns an iterator. Built-in iterables include Arrays, Strings, Maps, Sets, and NodeLists.
let fruits = ["Lemon", "Orange", "Grape"];
let iter = fruits[Symbol.iterator](); // Get the iterator
console.log(iter.next()); // { value: "Lemon", done: false }
console.log(iter.next()); // { value: "Orange", done: false }
console.log(iter.next()); // { value: "Grape", done: false }
console.log(iter.next()); // { value: undefined, done: true }Making a Custom Iterable Object
let range = {
from: 1,
to: 5,
[Symbol.iterator]() {
let current = this.from;
let last = this.to;
return {
next() {
if (current <= last) {
return { value: current++, done: false };
} else {
return { value: undefined, done: true };
}
}
};
}
};
for (let num of range) {
console.log(num); // 1, 2, 3, 4, 5
}
// Spread operator also works on iterables
console.log([...range]); // [1, 2, 3, 4, 5]What is a Generator?
A generator is a special type of function that can pause its execution midway and resume later. It is defined using function* (note the asterisk) and uses the yield keyword to produce values one at a time.
Real-life analogy: A generator is like a vending machine. Each time a button is pressed, one item comes out. The machine pauses and waits until the next button press.
function* simpleGenerator() {
yield "First";
yield "Second";
yield "Third";
}
let gen = simpleGenerator();
console.log(gen.next()); // { value: "First", done: false }
console.log(gen.next()); // { value: "Second", done: false }
console.log(gen.next()); // { value: "Third", done: false }
console.log(gen.next()); // { value: undefined, done: true }How yield Works
Each yield pauses the function and returns a value. Execution resumes from exactly the same point when next() is called again.
function* steps() {
console.log("Step 1 started");
yield "Step 1 complete";
console.log("Step 2 started");
yield "Step 2 complete";
console.log("Step 3 started");
yield "Step 3 complete";
}
let process = steps();
let s1 = process.next();
// "Step 1 started"
console.log(s1.value); // Step 1 complete
let s2 = process.next();
// "Step 2 started"
console.log(s2.value); // Step 2 completeGenerators Are Iterable
Generators automatically implement the iterable protocol, so they work directly with for...of loops and the spread operator.
function* countUp(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
for (let num of countUp(1, 5)) {
console.log(num); // 1, 2, 3, 4, 5
}
console.log([...countUp(10, 14)]); // [10, 11, 12, 13, 14]Infinite Sequence Generator
Generators are perfect for creating infinite sequences because they are lazy — they only produce values when asked.
function* infiniteCounter(start = 0) {
while (true) {
yield start++;
}
}
let counter = infiniteCounter(1);
console.log(counter.next().value); // 1
console.log(counter.next().value); // 2
console.log(counter.next().value); // 3
// Continues forever — only produces values when askedyield* — Delegating to Another Generator
yield* delegates the yield sequence to another generator or iterable.
function* part1() {
yield "A";
yield "B";
}
function* part2() {
yield "C";
yield "D";
}
function* combined() {
yield* part1();
yield* part2();
yield "E";
}
console.log([...combined()]); // ["A", "B", "C", "D", "E"]Passing Values into a Generator
A value can be passed back into a generator through next(value). The passed value becomes the result of the yield expression inside.
function* calculator() {
let a = yield "Enter first number:";
let b = yield "Enter second number:";
yield `Sum = ${a + b}`;
}
let calc = calculator();
console.log(calc.next().value); // Enter first number:
console.log(calc.next(10).value); // Enter second number:
console.log(calc.next(25).value); // Sum = 35Generator return() and throw()
return() — End the Generator Early
function* myGen() {
yield 1;
yield 2;
yield 3;
}
let g = myGen();
console.log(g.next()); // { value: 1, done: false }
console.log(g.return("Stop")); // { value: "Stop", done: true }
console.log(g.next()); // { value: undefined, done: true }throw() — Inject an Error into the Generator
function* safegen() {
try {
yield "Running...";
} catch (e) {
console.log("Error caught inside generator:", e);
}
}
let sg = safegen();
sg.next(); // { value: "Running...", done: false }
sg.throw("Something broke!"); // Error caught inside generator: Something broke!Generators for Async-like Flow (Concept)
Before async/await, generators were used to write asynchronous code in a synchronous style. Libraries like co used this pattern. Today, async/await replaces this need, but understanding it shows where async/await concepts came from.
// Conceptual example — simplified
function* fetchUserFlow() {
let user = yield fetch("https://jsonplaceholder.typicode.com/users/1");
let orders = yield fetch(`https://jsonplaceholder.typicode.com/posts?userId=${user.id}`);
return orders;
}
// In practice, use async/await for this pattern todayIterator vs Generator Comparison
| Feature | Iterator | Generator |
|---|---|---|
| Creation | Object with manual next() method | function* with yield |
| State management | Manual (track index) | Automatic (pauses at yield) |
| Infinite sequences | Possible but verbose | Natural and easy |
| Readability | More code | Concise |
| Iterable | Needs Symbol.iterator setup | Automatically iterable |
Practical Example — Paginated Data Generator
function* paginate(items, pageSize) {
for (let i = 0; i < items.length; i += pageSize) {
yield items.slice(i, i + pageSize);
}
}
let products = ["A", "B", "C", "D", "E", "F", "G", "H", "I"];
let pages = paginate(products, 3);
console.log(pages.next().value); // ["A", "B", "C"]
console.log(pages.next().value); // ["D", "E", "F"]
console.log(pages.next().value); // ["G", "H", "I"]
console.log(pages.next().done); // trueKey Points to Remember
- An iterator is any object with a
next()method that returns{ value, done } - An iterable implements
[Symbol.iterator]()to return an iterator - Built-in iterables include arrays, strings, maps, and sets
- Generators are functions defined with
function*that useyieldto pause and produce values - Calling a generator function returns a generator object — the function does not run immediately
- Each
next()call resumes the generator from where it last paused - Generators are automatically iterable — they work with
for...ofand spread - Generators are perfect for lazy evaluation, infinite sequences, and custom iteration logic
