Iteration underlies for...of, spread and destructuring, and generators make lazy and infinite sequences easy.
An object is iterable if it has a [Symbol.iterator]() method returning an iterator: an object with a next() method that returns { value, done }. Arrays, strings, Maps, Sets, NodeLists and arguments are iterable, and everything that consumes iterables — for...of, spread, array destructuring, Array.from, new Map(...), Promise.all — works with yours too.
Writing next() by hand is tedious. A generator function (function*) does it for you. Calling it returns a generator object without running the body; each next() resumes execution until the next yield, which produces a value and pauses:
function* range(start, end, step = 1) {
for (let i = start; i < end; i += step) yield i;
}
[...range(0, 10, 3)] // [0, 3, 6, 9]Generators keep their local state between calls, so they are naturally lazy: values are computed only when asked for. That makes infinite sequences possible, as long as the consumer stops. yield* other delegates to another iterable, handy for recursive structures like trees. A return inside ends iteration (done: true), and breaking out of for...of calls the generator's return() so its finally blocks run.
ES2025 adds iterator helpers on Iterator.prototype: map, filter, take, drop, flatMap, reduce, some, every, find, forEach and toArray. Unlike array methods they are lazy and work on any iterator, including infinite ones. Iterator.from(obj) wraps any iterable or iterator so it gets these methods.
Make a class iterable by defining *[Symbol.iterator]() { ... } as a generator method.
Example
function* naturals() {
let n = 1;
while (true) yield n++; // infinite, but lazy
}
const firstOddSquares = naturals()
.filter(n => n % 2) // ES2025 iterator helpers
.map(n => n * n)
.take(4)
.toArray();
console.log(firstOddSquares); // [ 1, 9, 25, 49 ]
class Tree {
constructor(value, children = []) { this.value = value; this.children = children; }
*[Symbol.iterator]() { // depth-first traversal
yield this.value;
for (const child of this.children) yield* child;
}
}
const tree = new Tree("root", [new Tree("a", [new Tree("a1")]), new Tree("b")]);
console.log([...tree]); // [ 'root', 'a', 'a1', 'b' ]
const it = [10, 20][Symbol.iterator]();
console.log(it.next(), it.next(), it.next());
// { value: 10, done: false } { value: 20, done: false } { value: undefined, done: true }Remember
- Iterable = has
[Symbol.iterator](); iterator = hasnext()returning{ value, done } - Generators pause at
yieldand are lazy — infinite sequences are fine - ES2025 iterator helpers (
map,filter,take,toArray...) chain lazily
Watch out for
- Spreading an infinite generator (
[...naturals()]) — it never ends.