JS Generator Function - What does next().value print twice?
This is a daily Javascript challenge from the CodeShot archive. Practice your knowledge of Generator Yield Next and improve your technical interview readiness.
function* gen() {
yield 1
yield 2
yield 3
}
const g = gen()
console.log(g.next().value)
console.log(g.next().value)
Detailed Explanation
Why This Question Matters
If you've spent any time with modern JavaScript, you've probably seen the function* syntax. Generators are one of those features that feel "magic" until they suddenly click. Most developers treat them as a niche curiosity or something only used inside libraries like Redux-Saga, but they actually solve a very specific problem: controlling the execution of a function.
The confusion usually stems from the fact that generators don't behave like regular functions. A normal function runs from top to bottom and returns a value. A generator, however, is a state machine. It pauses and resumes. If you don't understand how the iterator protocol works, you'll likely guess the wrong answer to this challenge because you're thinking in terms of standard function calls, not state transitions.
Understanding the Code
Let's look at the snippet:
First, notice the asterisk in function*. This tells JavaScript that this isn't a standard function; it's a generator function. When you call gen(), it doesn't actually execute the code inside the block. Instead, it returns a Generator Object.
Think of this object as a pointer. It knows exactly where the function is currently paused.
When we call g.next(), we're telling the engine: "Run the code until you hit the next yield keyword, then stop and give me the value."
Here is the internal play-by-play:
1. const g = gen(): We initialize the generator. The function is now "primed" but hasn't started running yet.
2. First g.next().value: The engine enters the function and runs until it hits yield 1. It pauses there and returns an object: { value: 1, done: false }. We access .value, so 1 is printed.
3. Second g.next().value: The engine resumes exactly where it left off. It doesn't restart from the top. It moves from the first yield to the second one: yield 2. It pauses again and returns { value: 2, done: false }. Thus, 2 is printed.
Finding the Correct Answer
The correct answer is Option B (1 and 2).
Why not other options?
- Why not 1 and 1? Because the generator maintains its state. It doesn't reset every time you call .next(). It remembers it already passed the first yield.
- Why not 1 and 3? The generator moves sequentially. It cannot skip yield 2 unless you explicitly call .next() multiple times.
- Why not undefined? The value property is explicitly set by the yield keyword.
If we called g.next().value a third time, we'd get 3. A fourth time? We'd get undefined because the function would finish, and the return object would be { value: undefined, done: true }.
Common Mistakes Developers Make
The biggest mistake is assuming gen() executes the body immediately. If you write a console.log inside the generator function but outside a yield, you'll notice it doesn't print until you call .next().
Another trip-up is forgetting that generators are single-use. Once a generator has been exhausted (i.e., it reached the end of the function), you can't "restart" it. If you need to iterate again, you have to create a new generator instance by calling gen() again.
Lastly, people often confuse generators with arrays. While you can loop over a generator using for...of, a generator doesn't store all its values in memory. It calculates the next value on the fly. This is a massive advantage for performance when dealing with large datasets.
Real-World Usage
You might be wondering, "Why would I ever use this instead of a simple array or a loop?"
The killer feature here is lazy evaluation. Imagine you have a dataset of 10 million records, and you need to process them one by one. If you put them in an array, you'll crash your browser's memory. With a generator, you only ever have one record in memory at a time.
In production, you'll see this in:
1. Custom Iterables: Creating a data structure that can be looped over without exposing the internal storage.
2. Async Flow Control: Before async/await became the standard, generators (combined with promises) were used to write asynchronous code that looked synchronous.
3. Infinite Sequences: You can write a generator that produces an infinite sequence of IDs or Fibonacci numbers without causing a stack overflow, because it only produces the next value when requested.
Key Takeaways
- A generator function returns an iterator, not a value.
- yield is a pause button. It tells the function to stop and hand a value back to the caller.
- .next() is the play button. It resumes execution until the next yield or the end of the function.
- Generators maintain their internal state, making them perfect for handling streams of data or complex state machines without polluting the global scope.
Why this matters
Understanding Generator Yield Next is crucial for passing technical interviews. In real-world applications, this concept often leads to subtle bugs if not handled correctly. For more details, you can always refer to the official MDN Documentation.