Understand the iterator protocol and create custom iterables with generator functions using function* and yield.
An iterator is any object that implements a next() method. Each call to next() returns an object with two properties:
value — the next value in the sequence.done — true if the sequence is finished, false otherwise.// A simple manual iterator
const iterator = {
current: 1,
last: 3,
next() {
if (this.current <= this.last) {
return { value: this.current++, done: false };
}
return { value: undefined, done: true };
}
};
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }Many built-in JavaScript types are iterable: arrays, strings, Maps, Sets, NodeLists, and more. The for...of loop, spread operator (...), and destructuring all use the iterator protocol under the hood.
for (const char of 'hello') {
console.log(char); // 'h', 'e', 'l', 'l', 'o'
}An object is iterable if it has a method at the key Symbol.iterator that returns an iterator.
const range = {
from: 1,
to: 5,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
if (current <= last) {
return { value: current++, done: false };
}
return { value: undefined, done: true };
}
};
}
};Now you can use this custom iterable with any iteration feature:
// for...of
for (const n of range) {
console.log(n); // 1, 2, 3, 4, 5
}
// Spread operator
const nums = [...range]; // [1, 2, 3, 4, 5]
// Destructuring
const [a, b, c] = range; // a=1, b=2, c=3
// Array.from
const arr = Array.from(range); // [1, 2, 3, 4, 5]The iterable protocol is what makes JavaScript's iteration features so flexible and consistent. Any object that implements [Symbol.iterator]() can participate in the iteration ecosystem.
Writing iterators manually is verbose. Generator functions provide a much simpler way to create iterators.
A generator function is declared with function* (note the asterisk). Inside, you use the yield keyword to produce values.
function* countUp(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
const gen = countUp(1, 3);
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 }Key features:
yield pauses the function and produces a value.next() resumes from where it paused.for (const n of countUp(1, 5)) {
console.log(n); // 1, 2, 3, 4, 5
}
const nums = [...countUp(10, 15)]; // [10, 11, 12, 13, 14, 15]yield* delegates to another iterable:
function* combined() {
yield* [1, 2, 3];
yield* 'abc';
}
// Produces: 1, 2, 3, 'a', 'b', 'c'Generators are particularly powerful for scenarios that benefit from lazy evaluation — producing values only when needed.
Infinite sequences — generate values forever, consuming them one at a time:
function* ids() {
let id = 1;
while (true) {
yield id++;
}
}
const idGen = ids();
console.log(idGen.next().value); // 1
console.log(idGen.next().value); // 2
console.log(idGen.next().value); // 3
// Can continue foreverFibonacci sequence:
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}Lazy processing of large datasets — avoid loading everything into memory:
function* filterLarge(items, predicate) {
for (const item of items) {
if (predicate(item)) yield item;
}
}Pagination:
function* paginate(items, pageSize) {
for (let i = 0; i < items.length; i += pageSize) {
yield items.slice(i, i + pageSize);
}
}Async generators combine generators with async/await for streaming async data:
async function* fetchPages(url) {
let page = 1;
while (true) {
const response = await fetch(`${url}?page=${page}`);
const data = await response.json();
if (data.length === 0) return;
yield data;
page++;
}
}<div id="output"></div>
<script>
// Fibonacci generator
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
// Take the first N values from a generator
function take(gen, n) {
const result = [];
for (const value of gen) {
result.push(value);
if (result.length >= n) break;
}
return result;
}
const first10 = take(fibonacci(), 10);
// Range generator
function* range(start, end, step = 1) {
for (let i = start; i <= end; i += step) {
yield i;
}
}
const evens = [...range(0, 20, 2)];
const output = document.getElementById('output');
output.innerHTML = `
<p>First 10 Fibonacci: [${first10.join(', ')}]</p>
<p>Even numbers 0-20: [${evens.join(', ')}]</p>
`;
</script>What does the yield keyword do in a generator?