Q109: Symbols, Iterators and Generators
What Interviewers Want To Evaluate
This question checks whether you understand advanced language protocols. Interviewers want symbols, well-known symbols, iterable protocol, iterator protocol, generator functions, lazy sequences, custom iteration, and practical uses.
Senior answers should explain how for...of, spread, destructuring, maps, sets, and async data flows relate to these protocols.
Short Interview Answer
A Symbol is a unique primitive value often used as a non-colliding property key. Well-known symbols let objects customize language behavior. Symbol.iterator makes an object iterable by returning an iterator. An iterator has a next() method that returns { value, done }. Generator functions provide a convenient way to create iterators using function* and yield.
Detailed Interview Answer
Symbols are primitive values that are guaranteed to be unique.
const id = Symbol("id");
const user = {
[id]: "u1",
name: "Asha",
};
Even if two symbols have the same description, they are different values.
Symbol("id") === Symbol("id"); // false
Symbol Property Keys
Symbols can be used as object property keys.
They are not returned by Object.keys, but they are still real properties.
const keys = Object.keys(user);
const symbols = Object.getOwnPropertySymbols(user);
Use Reflect.ownKeys when you need both string keys and symbol keys.
Well-Known Symbols
JavaScript defines well-known symbols that customize language behavior.
Examples include:
Symbol.iterator
Symbol.asyncIterator
Symbol.toStringTag
Symbol.hasInstance
Symbol.toPrimitive
The most common interview example is Symbol.iterator.
Iterable Protocol
An object is iterable if it has a method at Symbol.iterator that returns an iterator.
const range = {
start: 1,
end: 3,
[Symbol.iterator]() {
let current = this.start;
const end = this.end;
return {
next() {
if (current <= end) {
return { value: current++, done: false };
}
return { value: undefined, done: true };
},
};
},
};
console.log([...range]); // [1, 2, 3]
Iterator Protocol
An iterator is an object with a next method.
Every call to next returns an object with:
value: the produced value
done: whether iteration has finished
This protocol powers for...of, spread, destructuring, Array.from, maps, and sets.
Generators
Generators make iterator creation easier.
function* createRange(start, end) {
for (let value = start; value <= end; value += 1) {
yield value;
}
}
console.log([...createRange(1, 3)]); // [1, 2, 3]
Calling a generator function does not run the function body immediately. It returns a generator object. The body runs as values are requested.
Lazy Execution
Generators are lazy. They produce values on demand.
function* ids() {
let id = 1;
while (true) {
yield id++;
}
}
const generator = ids();
generator.next().value; // 1
generator.next().value; // 2
This can represent large or infinite sequences without creating all values at once.
yield and Control Flow
yield pauses the generator. The next call resumes execution after the previous yield.
function* steps() {
yield "parse";
yield "validate";
yield "submit";
}
This is different from returning an array because the function can pause and resume.
Custom Collection Example
class Playlist {
constructor(songs) {
this.songs = songs;
}
*[Symbol.iterator]() {
for (const song of this.songs) {
yield song.title;
}
}
}
const playlist = new Playlist([{ title: "Intro" }, { title: "Finale" }]);
console.log([...playlist]);
This lets a domain object participate naturally in iteration syntax.
Async Iteration Preview
Async iterables use Symbol.asyncIterator and are consumed with for await...of.
They are useful for streams, paginated data, logs, and events.
for await (const chunk of stream) {
console.log(chunk);
}
This handbook will go deeper into async iteration later.
Common Mistakes
A common mistake is thinking symbols are private. Symbol keys are less likely to collide, but they can still be discovered.
Another mistake is confusing iterable and iterator. An iterable can create an iterator. An iterator produces values.
Senior Trade-Offs
Custom iterables are elegant when the domain naturally has sequence behavior. They can be confusing if used to hide business logic or side effects.
Use them for collections, lazy pipelines, streams, and library APIs where iteration is a clear user expectation.
Debugging Workflow
When iteration fails:
check whether object has Symbol.iterator
check whether Symbol.iterator returns an iterator
check next returns value and done
check whether generator has been exhausted
check whether async iteration is required
check whether spread or for...of expects sync iteration
Interview Framing
Define symbol uniqueness, then connect Symbol.iterator to for...of and spread. Use a generator example because it is the cleanest way to show the protocol.
Revision Notes
Symbols are unique property keys. Iterables expose Symbol.iterator. Iterators produce { value, done }. Generators create iterators with yield.
Key Takeaways
Iteration in JavaScript is protocol-based. Once you understand the protocols, features like spread, destructuring, maps, sets, and generators feel connected.
Follow-Up Questions
- What makes a symbol unique?
- Are symbols truly private?
- What is a well-known symbol?
- What makes an object iterable?
- What does an iterator return from
next? - What syntax consumes iterables?
- What does a generator function return?
- Why are generators considered lazy?
- What is the difference between iterable and iterator?
- When would you build a custom iterable?
How I Would Answer This In A Real Interview
I would explain symbols as unique keys, then focus on Symbol.iterator. I would show how an iterable returns an iterator and how a generator makes that easier. Finally, I would connect the idea to spread, for...of, custom collections, and lazy sequences.