Skip to content
Plurafy

Generators and Iterators

Understanding generators, iterables, and custom iteration protocols

You need to create a function that yields values one at a time on demand instead of returning all at once. How can you create an infinite sequence that’s memory efficient?

Generators are functions that can be paused and resumed, yielding values one at a time. They implement the iterator protocol, enabling lazy evaluation and memory-efficient iteration.

function* countUp() {
console.log('Starting');
yield 1;
console.log('After 1');
yield 2;
console.log('After 2');
yield 3;
}
const gen = countUp();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }
// Generators are iterable
for (const value of countUp()) {
console.log(value); // 1, 2, 3
}
function* infiniteNumbers() {
let n = 0;
while (true) {
yield n++;
}
}
// Memory efficient - doesn't create entire array
const numGen = infiniteNumbers();
console.log(numGen.next().value); // 0
console.log(numGen.next().value); // 1
console.log(numGen.next().value); // 2
// Get first n numbers
function* take(iterable, n) {
let count = 0;
for (const value of iterable) {
if (count++ >= n) break;
yield value;
}
}
console.log([...take(infiniteNumbers(), 5)]); // [0, 1, 2, 3, 4]
// Fibonacci sequence
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
console.log([...take(fibonacci(), 10)]); // First 10 Fibonacci numbers
// Iterable: Has Symbol.iterator method
// Iterator: Has next() method that returns { value, done }
// String is iterable
const str = 'hello';
for (const char of str) {
console.log(char); // h, e, l, l, o
}
// Creating custom iterable
const range = {
start: 1,
end: 5,
[Symbol.iterator]() {
let current = this.start;
const end = this.end;
return {
next() {
if (current <= end) {
return { value: current++, done: false };
}
return { done: true };
}
};
}
};
for (const num of range) {
console.log(num); // 1, 2, 3, 4, 5
}
function* dialog() {
const name = yield 'What is your name?';
console.log('Name:', name);
const age = yield 'What is your age?';
console.log('Age:', age);
yield `${name} is ${age} years old`;
}
const gen = dialog();
console.log(gen.next()); // { value: 'What is your name?', done: false }
console.log(gen.next('Alice')); // Sends 'Alice' to previous yield
console.log(gen.next(30)); // Sends 30 to previous yield
console.log(gen.next()); // { value: '...', done: true }
function* inner() {
yield 1;
yield 2;
}
function* outer() {
yield 'a';
yield* inner(); // Delegate to another generator
yield 'b';
}
console.log([...outer()]); // ['a', 1, 2, 'b']
// Tree traversal example
function* traverse(node) {
yield node.value;
if (node.left) yield* traverse(node.left);
if (node.right) yield* traverse(node.right);
}
function* lazyDataLoader(dataSource) {
let page = 1;
while (true) {
const data = fetchPage(page);
if (data.length === 0) break;
for (const item of data) {
yield item;
}
page++;
}
}
// Simulate fetching
function fetchPage(page) {
if (page > 3) return [];
return Array.from({ length: 10 }, (_, i) => ({
id: (page - 1) * 10 + i,
title: `Item ${i}`
}));
}
const loader = lazyDataLoader('api/items');
// Load data on demand
console.log(loader.next()); // First item
console.log(loader.next()); // Second item
// Data is fetched page by page only as needed
function* errorHandling() {
try {
yield 1;
throw new Error('Generator error');
yield 2;
} catch (error) {
console.log('Caught:', error.message);
yield 3;
}
}
const gen = errorHandling();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.throw(new Error('External error')));
// Caught: External error
// { value: 3, done: false }
// Problem: Recursive array processing
const data = [1, [2, 3, [4, 5]]];
// ❌ Complex with nested loops
function flatWithLoops(arr) {
const result = [];
for (let item of arr) {
if (Array.isArray(item)) {
result.push(...flatWithLoops(item));
} else {
result.push(item);
}
}
return result;
}
// ✅ Clean with generators
function* flatWithGenerator(arr) {
for (const item of arr) {
if (Array.isArray(item)) {
yield* flatWithGenerator(item);
} else {
yield item;
}
}
}
console.log([...flatWithGenerator(data)]); // [1, 2, 3, 4, 5]
// Creating all values upfront
function getAllNumbers() {
const result = [];
for (let i = 0; i < 1000000; i++) {
result.push(i);
}
return result;
}
// Lazy generation - only creates values as needed
function* getNumbersLazy() {
for (let i = 0; i < 1000000; i++) {
yield i;
}
}
// Memory efficient: only first 10 items created
const first10 = [...take(getNumbersLazy(), 10)];