iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Iterator

The Iterator pattern exposes elements of a collection one at a time without revealing the underlying structure. In modern languages it’s baked into the standard library — for...of, generators, IEnumerable, Iterator trait — so you usually implement it rather than recreate the dispatch.

Iterator protocol, generators, ranges

EXAMPLE
// 1) The Iterator protocol (JavaScript)
// An object with a next() method that returns { value, done }.
const iter = {
    i: 0,
    next() {
        if (this.i < 3) return { value: this.i++, done: false };
        return { value: undefined, done: true };
    },
};
console.log(iter.next());   // { value: 0, done: false }
console.log(iter.next());   // { value: 1, done: false }
console.log(iter.next());   // { value: 2, done: false }
console.log(iter.next());   // { value: undefined, done: true }

// 2) Iterable — has [Symbol.iterator]() that returns an Iterator
class Range {
    constructor(start, end, step = 1) {
        this.start = start; this.end = end; this.step = step;
    }
    [Symbol.iterator]() {
        let i = this.start;
        const { end, step } = this;
        return {
            next() {
                if (i < end) return { value: i, done: false }; i += step; return arguments.length ? { value: i - step, done: false } : { value: i - step, done: false };
            },
        };
    }
}

// Used with for…of, spread, destructuring
for (const x of new Range(0, 5)) console.log(x);   // 0, 1, 2, 3, 4
const arr = [...new Range(0, 5)];                   // [0,1,2,3,4]

// 3) Generators — built-in syntax for iterators
function* counter(start, end, step = 1) {
    for (let i = start; i < end; i += step) yield i;
}

for (const n of counter(0, 5)) console.log(n);     // 0..4

// Generators ARE iterators AND iterables.
[...counter(0, 3)];                                  // [0, 1, 2]

// 4) Infinite iterators with take()
function* naturals() {
    let i = 0;
    while (true) yield i++;
}

function* take(it, n) {
    let count = 0;
    for (const x of it) {
        if (count++ >= n) break;
        yield x;
    }
}

[...take(naturals(), 5)];                            // [0, 1, 2, 3, 4]

// 5) Composable iterator helpers (built-in in TC39 Iterator Helpers / Array methods)
naturals()
    .map((x) => x * x)
    .filter((x) => x % 2 === 0)
    .take(5)
    .toArray();
// Browser/Node support varies; use a polyfill or library for older runtimes.

// 6) Custom iterable — Tree traversal
class TreeNode {
    constructor(value) { this.value = value; this.children = []; }
    add(child) { this.children.push(child); return this; }

    *[Symbol.iterator]() {                            // depth-first
        yield this.value;
        for (const c of this.children) yield* c;
    }
}

const root = new TreeNode('a').add(new TreeNode('b').add(new TreeNode('d'))).add(new TreeNode('c'));
for (const v of root) console.log(v);                // a, b, d, c

// 7) Async iterators — for await…of
async function* paginate(url) {
    let next = url;
    while (next) {
        const r = await fetch(next).then((r) => r.json());
        for (const item of r.items) yield item;
        next = r.nextUrl;
    }
}

for await (const user of paginate('/api/users?page=1')) {
    console.log(user);
}

// Async iterators are perfect for streaming APIs, pagination, message queues.

// 8) In other languages
//
// Python — __iter__ / __next__ or 'yield'
// class Range:
//     def __init__(self, start, end): self.start, self.end = start, end
//     def __iter__(self): self.i = self.start; return self
//     def __next__(self):
//         if self.i >= self.end: raise StopIteration
//         self.i += 1
//         return self.i - 1

// Rust — Iterator trait
// impl Iterator for Counter { type Item = u32; fn next(&mut self) -> Option<u32> { … } }

// C# — IEnumerable<T> / IEnumerator<T> / yield return
// public IEnumerable<int> Counter() { for (int i = 0; i < 10; i++) yield return i; }

// Java — Iterator<E> / Iterable<E>

// Go — no language-level iterator; use channels or range over a slice
// (Go 1.23+ has range-over-function with iter package)

// 9) When iterator is the right pattern
// • Walk a collection without exposing internal structure (trees, graphs, custom containers)
// • Stream data lazily — avoids materialising huge sequences
// • Compose pipelines without intermediate arrays
// • Pause / resume via generators (state machines, coroutines)

// 10) When NOT to bother
// • Plain arrays + map/filter/forEach are usually enough
// • Simple loops over fixed-size data — direct for(let i = 0; i < n; i++) is fine

// 11) Streams vs iterators
// • Streams (Node Readable, RxJS Observable) — push model; producer decides
// • Iterators — pull model; consumer pulls next()
// • Async iterators bridge both: consumer pulls, but the value arrives asynchronously

// 12) Combine with state machines
function* stateMachine(events) {
    let state = 'idle';
    while (true) {
        const event = yield state;
        switch (state) {
            case 'idle':    if (event === 'start') state = 'running'; break;
            case 'running': if (event === 'stop')  state = 'idle';    break;
        }
    }
}

const sm = stateMachine();
sm.next();                       // start
sm.next('start');                // 'running'
sm.next('stop');                 // 'idle'

// 13) Common bugs
// • Returning the same iterator instance from [Symbol.iterator]() — can't loop twice; return a NEW one
// • Mutating the underlying collection while iterating — undefined behaviour; copy or use defensive copies
// • Generator with leaked resources — use try/finally inside the generator body to clean up
// • Async generator without cancellation — pulls forever; pair with AbortSignal
// • for…of on an Object — Objects aren't iterable; use Object.entries / keys / values
// • Returning { done: false } with no value — confuses callers; use { value: undefined, done: true } when done
// • Spread of infinite iterator — stack overflow; cap with take()
// • Calling .next() after done: true — should keep returning done; some custom iterators throw

Why it matters

Reach for the iterator pattern when traversal logic shouldn’t leak the underlying structure — trees, graphs, custom containers, infinite sequences, streaming pagination. Use generators (function*) for synchronous iteration and async function* + for await…of for streams. Compose with map/filter/take for lazy pipelines.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// JS uses Symbol.iterator.
class Range {
    constructor(start, end) { this.start = start; this.end = end; }
    *[Symbol.iterator]() {
        for (let i = this.start; i < this.end; i++) yield i;
    }
}
for (const n of new Range(0, 5)) console.log(n);
Try it Yourself »

Discussion

Loading…