Suggest an editImprove this articleRefine the answer for “What is symbol.iterator and why is it needed”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**`Symbol.iterator`** - a built-in symbol that defines how JavaScript iterates over an object. `for...of`, spread, and `Array.from()` all call `obj[Symbol.iterator]()` to get values one by one. ```javascript const range = { [Symbol.iterator]() { let i = 1; return { next: () => i <= 3 ? { value: i++, done: false } : { done: true } }; } }; for (const n of range) console.log(n); // 1, 2, 3 console.log([...range]); // [1, 2, 3] ``` **Key:** any object with `[Symbol.iterator]()` works in `for...of`, spread, and destructuring.Shown above the full answer for quick recall.Answer (EN)Image**`Symbol.iterator`** - a built-in symbol in JavaScript that defines how an object produces a sequence of values for `for...of`, spread (`...`), `Array.from()`, and destructuring. ## Theory ### TL;DR - `Symbol.iterator` works like a vending machine's dispense slot: it defines the interface for getting values one at a time - Arrays, strings, Sets, and Maps have it built in; plain objects do not - Any object with `[Symbol.iterator]()` works in `for...of`, spread, and destructuring - The method must return an iterator: an object with `next()` that returns `{ value, done }` - Need custom iteration order or lazy data loading? Implement it. Otherwise, use Array or Set directly ### Quick example ```javascript // Plain object - no Symbol.iterator, throws TypeError const obj = { a: 1, b: 2 }; for (const v of obj) console.log(v); // TypeError: obj is not iterable // Add Symbol.iterator - now works everywhere const iterable = { data: [10, 20, 30], *[Symbol.iterator]() { // generator handles next() for you for (const v of this.data) yield v; } }; for (const v of iterable) console.log(v); // 10, 20, 30 console.log([...iterable]); // [10, 20, 30] ``` The generator syntax (`function*`) handles the `next()` plumbing automatically. The result is the same as writing `{ next: () => ({ value, done }) }` by hand. ### How the iteration protocol works `for...of` does three things. It calls `obj[Symbol.iterator]()` once to get an iterator. Then it calls `iterator.next()` on each loop step. It stops when `next()` returns `{ done: true }`. Two separate roles exist here. The **iterable** is the object with `[Symbol.iterator]()`. The **iterator** is what that method returns: a stateful `{ next() }` object. Arrays are both at once because `arr[Symbol.iterator]()` returns the array itself with `next()` attached. That separation matters in practice. The iterator is stateful and exhausts once. The iterable produces a fresh iterator on every call. ### Iterable vs iterator ```javascript const arr = [1, 2, 3]; const it = arr[Symbol.iterator](); // get a fresh iterator console.log(it.next()); // { value: 1, done: false } console.log(it.next()); // { value: 2, done: false } console.log(it.next()); // { value: 3, done: false } console.log(it.next()); // { value: undefined, done: true } ``` ### When to implement Symbol.iterator Implement it when you have a data structure that should behave like a sequence: a range, a paginated result set, a tree traversal, or a DOM wrapper. Skip it if `Array` or `Set` already fits. A practical signal: if you keep writing `getAll()` methods that return arrays, and callers always iterate the result, putting `[Symbol.iterator]` directly on the object is cleaner. ### How the engine handles this V8 checks `obj[Symbol.iterator]` at the start of `for...of`. If the property is missing or not callable, it throws `TypeError: obj is not iterable` before the loop body runs at all. Once it has the iterator, it calls `next()` in a loop, reading `value` and `done` from each result. Generators compile to state machines in V8 bytecode: each `yield` saves the execution position and local variables, and `next()` resumes from that saved point. The lazy nature of iterators matters here. A generator that fetches from a network page won't hit the network until `next()` is actually called. No preloading, no full array allocated in memory. ### Common mistakes **Reusing an exhausted iterator:** ```javascript const it = myIterable[Symbol.iterator](); // cached iterator, not the iterable for (const v of it) console.log(v); // 1, 2, 3 for (const v of it) console.log(v); // nothing - already exhausted ``` `for...of` on an iterable calls `[Symbol.iterator]()` fresh each time. But `it` is already an iterator. Done is done. Fix: iterate the original object, not the cached iterator. **Mutating source during iteration:** ```javascript const obj = { data: [1, 2, 3], *[Symbol.iterator]() { yield* this.data; } }; for (const v of obj) { obj.data.shift(); // mutates the live array mid-loop console.log(v); // logs 1, 3 - skips 2 } ``` The generator reads from a live reference. Clone data first if you need to mutate during the loop. **Using sync iteration on async generators:** ```javascript async function* fetchItems() { yield await Promise.resolve(1); } for (const v of fetchItems()) {} // TypeError for await (const v of fetchItems()) {} // correct ``` `for...of` expects a sync `next()`. Async generators need `for await...of` and use `[Symbol.asyncIterator]` internally, not `Symbol.iterator`. **Infinite iterator without a break:** ```javascript const counter = { *[Symbol.iterator]() { let i = 0; while (true) yield i++; } }; for (const n of counter) { if (n > 10) break; // break triggers iterator.return() internally console.log(n); } // Without the break, this never terminates ``` ### Real-world usage - **Node.js streams:** `Readable.from(iterable)` (Node 12+) converts any custom iterator to a readable stream directly - **`Array.from`:** uses `[Symbol.iterator]` internally, same as spread - **Destructuring:** `const [a, b] = myIterable` calls `[Symbol.iterator]` under the hood - **`Promise.all` / `Promise.race`:** both accept any iterable, not just arrays - **Custom ranges:** `for (const i of range(1, 100))` reads better than a manual `for` loop with an index I've found `[Symbol.iterator]` most useful on database cursor wrappers: when a DB client returns a cursor, attaching the iterator protocol to it lets downstream code stay clean without any interface changes. ### Follow-up questions **Q:** What is the difference between an iterable and an iterator? **A:** An iterable has `[Symbol.iterator]()` that returns a fresh iterator on each call. An iterator is the stateful `{ next() }` object. Arrays are both: `arr[Symbol.iterator]()` returns `arr` itself with `next()` attached. **Q:** How would you implement a finite range without generators? **A:** `const range = (end) => ({ [Symbol.iterator]() { let i = 0; return { next: () => i < end ? { value: i++, done: false } : { done: true } }; } });` **Q:** Why does `for...of` throw on plain objects but `for...in` works? **A:** `for...in` uses the internal property enumeration mechanism, separate from the iteration protocol. `for...of` strictly requires `[Symbol.iterator]`. Two different specs, two different checks. **Q:** What happens when you `break` inside `for...of` over a generator? **A:** The engine calls `iterator.return()` if it exists, which triggers the generator's `finally` block. This lets generators clean up resources on early exit: close file handles, cancel network requests. **Q:** (Senior) How does V8 compile a generator function? **A:** As a state machine. Each `yield` splits the function body into numbered states. `next()` jumps to the current state via bytecode dispatch, runs until the next `yield`, saves locals and the state index into the `GeneratorObject` closure, then suspends. ## Examples ### Range iterator with manual next() ```javascript // Explicit next() - no generator syntax function range(start, end) { return { [Symbol.iterator]() { let current = start; return { next() { return current <= end ? { value: current++, done: false } : { value: undefined, done: true }; } }; } }; } for (const n of range(1, 5)) console.log(n); // 1 2 3 4 5 console.log([...range(1, 5)]); // [1, 2, 3, 4, 5] const [first, second] = range(10, 20); console.log(first, second); // 10 11 ``` All three usages - `for...of`, spread, and destructuring - call `[Symbol.iterator]()` internally. The same object works in all three contexts without any extra code. ### Paginated API fetch with async generator ```javascript // Each yield sends one user - caller never sees pagination async function* fetchAllUsers(baseUrl) { let page = 1; while (true) { const res = await fetch(`${baseUrl}?page=${page++}&limit=10`); const data = await res.json(); if (!data.length) return; // no more pages, done yield* data; // stream items from this page one by one } } // Process users without loading all pages into memory first for await (const user of fetchAllUsers('/api/users')) { console.log(user.name); if (user.role === 'admin') break; // exits early, no extra fetch } ``` This pattern works in Express SSE routes and Next.js data layers where full preloading is not acceptable. Note: async generators use `Symbol.asyncIterator`, not `Symbol.iterator`. The `for await...of` loop handles that automatically. ### Tree traversal as an iterable class ```javascript class TreeNode { constructor(value, children = []) { this.value = value; this.children = children; } // Depth-first traversal via generator delegation *[Symbol.iterator]() { yield this.value; for (const child of this.children) { yield* child; // delegate to child's [Symbol.iterator] } } } const tree = new TreeNode(1, [ new TreeNode(2, [new TreeNode(4), new TreeNode(5)]), new TreeNode(3) ]); console.log([...tree]); // [1, 2, 4, 5, 3] ``` `yield*` delegates to another iterable: it calls `child[Symbol.iterator]()` and exhausts it before continuing. This is generator delegation - it composes tree traversals cleanly without manual stack management.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.