const range = {
[Symbol.iterator]() {
let i = 1;
return { next: () => i <= 3 ? { value: i++, done: false } : { value: undefined, done: true } };
}
};
console.log([...range]); // [1, 2, 3]
Topics
37
ArrayBuffer, TypedArrays & Binary Data
Arrays & Array Methods
Async Iterators & Streams
Browser Storage & Web APIs
Classes & Class Syntax
Date, Time & Internationalization (Intl API)
Debugging, Testing & Tooling
Design Patterns in JavaScript
Destructuring, Spread & Rest
DOM & Events
Error Handling
ES Modules
Event Loop & Concurrency
Functional Programming
Iterators & Generators
JSON & Data Serialization
Map, Set, WeakMap & WeakSet
Memory Management & Garbage Collection
Networking: Fetch, XHR, WebSockets & CORS
Numbers, Math & BigInt
Objects, Property Descriptors & Immutability
Optional Chaining & Nullish Coalescing
Package Management, Bundlers & Transpilation (npm, Webpack/Vite, Babel)
Performance Optimization: Debouncing, Throttling & Memoization
Promises & async/await
Prototypes & Inheritance
Proxy & Reflect
Regular Expressions
Scope, Hoisting & Closures
Security: XSS, CSRF & Content Security Policy
Service Workers & Progressive Web Apps
Strings & Template Literals
Symbols & Well-Known Symbols
this & Binding
Types & Coercion
Web Components & Custom Elements
Web Workers & Multithreading
Iterators & Generators
10 questions found
An object is iterable if it implements the Symbol.iterator method, which returns an iterator object with a next() method. This is what powers for...of loops, spread syntax, and destructuring on that object.
Real-world example
Making a custom collection class work seamlessly with for...of and spread, just like a built-in Array.
Types & Coercion
Add an asterisk after 'function' to make it a generator; calling it doesn't run the body immediately but returns an iterator, and each yield pauses execution, resuming from that point on the next next() call.
function* countTo3() {
yield 1;
yield 2;
yield 3;
}
const gen = countTo3();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
Real-world example
Lazily generating a sequence of values one at a time instead of computing and storing them all upfront.
Types & Coercion
Yes — a generator function's return value is itself an iterable (it implements Symbol.iterator returning itself), so for...of can consume it directly, automatically calling next() and stopping when done is true.
function* countTo3() { yield 1; yield 2; yield 3; }
for (const n of countTo3()) {
console.log(n); // 1, 2, 3
}
Real-world example
Iterating over a generator-produced sequence with the same clean syntax used for arrays.
Async Iterators & Streams
When a generator hits yield, execution pauses immediately and control returns to the caller with an object { value, done: false } — the generator's internal state (including local variables) is preserved until next() is called again, which resumes execution right after that yield.
function* logger() {
console.log('before');
const received = yield 'paused-value';
console.log('resumed with:', received);
}
const it = logger();
it.next(); // logs 'before', returns { value: 'paused-value', done: false }
it.next('hello'); // logs 'resumed with: hello'
Real-world example
Building a step-by-step wizard flow where each step waits for external input before continuing.
Closures
yield* fully delegates iteration to another iterable (including another generator), yielding each of its values in turn as if they were yielded directly by the outer generator — useful for composing generators without manually re-yielding each value.
function* inner() { yield 'a'; yield 'b'; }
function* outer() {
yield 1;
yield* inner(); // delegates to inner()
yield 2;
}
[...outer()]; // [1, 'a', 'b', 2]
Real-world example
Composing a large generator out of smaller, reusable generator building blocks.
Design Patterns in JavaScript
A generator can loop forever internally (e.g. while (true)) because it only computes and yields one value at a time when next() is called — nothing is precomputed, so it never actually tries to build an infinite array in memory.
function* naturalNumbers() {
let n = 1;
while (true) {
yield n++;
}
}
const gen = naturalNumbers();
gen.next().value; // 1
gen.next().value; // 2
Real-world example
Generating an ID sequence or lazily producing values for an infinite scroll feed, only as needed.
Async Iterators & Streams
How do you use generator.throw() and generator.return() to control a running generator externally?
Advancedgenerator.throw(err) resumes the generator by throwing err at the current yield point, letting the generator's own try/catch handle it. generator.return(value) forces the generator to terminate immediately as if a return statement executed there, running any finally blocks first.
function* gen() {
try {
yield 1;
} catch (err) {
console.log('caught:', err.message);
yield 2;
}
}
const it = gen();
it.next(); // { value: 1, done: false }
it.throw(new Error('oops')); // logs 'caught: oops', returns { value: 2, done: false }
Real-world example
Cancelling an in-progress generator-based task pipeline cleanly from outside code.
Error Handling
How were generators historically used to write asynchronous code before async/await existed?
AdvancedGenerators combined with a driver function (like early versions of co or redux-saga) let you yield Promises and have the driver automatically call .next() again once each Promise resolved, producing async-looking code with synchronous-looking syntax — the direct conceptual predecessor to async/await.
function run(genFn) {
const gen = genFn();
function step(input) {
const { value, done } = gen.next(input);
if (!done) value.then(step);
}
step();
}
run(function* () {
const data = yield fetch('/api').then(r => r.json());
console.log(data);
});
Real-world example
Understanding why redux-saga still uses generators today, and how async/await effectively standardized this pattern.
Promises & async/await
How would you implement a custom Symbol.iterator for a tree data structure to enable depth-first traversal with for...of?
AdvancedImplement [Symbol.iterator]() as a generator method (using function*), recursively yielding each node's value and delegating to child nodes with yield* — this lets a complex, non-linear structure be consumed with simple, idiomatic for...of syntax.
class TreeNode {
constructor(value, children = []) {
this.value = value;
this.children = children;
}
*[Symbol.iterator]() {
yield this.value;
for (const child of this.children) {
yield* child; // delegates to each child's iterator
}
}
}
Real-world example
Traversing a nested comment thread or file-system-like tree structure with a plain for...of loop.
Design Patterns in JavaScript
What's the performance and memory advantage of a generator-based lazy pipeline over building intermediate arrays with map/filter?
AdvancedChaining array methods like map().filter() creates a full new intermediate array at each step, even if you only need a few results. A generator-based pipeline processes and yields one item at a time end-to-end, avoiding intermediate arrays entirely — especially valuable for large or infinite sequences where you only consume a subset.
function* map(iter, fn) { for (const x of iter) yield fn(x); }
function* filter(iter, pred) { for (const x of iter) if (pred(x)) yield x; }
function* take(iter, n) { let i = 0; for (const x of iter) { if (i++ >= n) return; yield x; } }
const result = [...take(filter(map(hugeSource, x => x * 2), x => x > 100), 5)];
Real-world example
Processing the first 5 matching results from a huge or infinite dataset without transforming the entire dataset first.
Functional Programming