Summary
The generator appears to “reverse” an iterable, but the behavior is actually an artifact of iterator exhaustion, recursive delegation, and JavaScript’s iteration semantics. The function does not truly reverse anything; instead, it repeatedly drains the same iterator until only the last value remains available, producing an output that looks reversed.
Root Cause
The core issue is that the function:
for (const item of iterable) {
yield* reverseLazy(iterable);
yield item;
}
uses the same iterator instance (iterable) in every recursive call. Because iterators are stateful and consumable, each recursive layer advances the same underlying cursor.
Key points:
iterableis not cloned; every recursion shares the same iterator.for...ofconsumes the iterator until it is exhausted.- Recursive calls run before yielding the current item, so deeper recursion consumes more of the iterator first.
- By the time the deepest recursion returns, only the last element remains, so it is yielded first.
Why This Happens in Real Systems
Real-world iterator bugs often arise from:
- Shared mutable state — multiple consumers reading from the same iterator.
- Unexpected recursion over a consumable resource.
- Assuming an iterable is reusable when it is actually a one-shot iterator.
- *Delegation (`yield`) hiding control flow**, making it unclear when iteration advances.
These patterns frequently appear in streaming systems, lazy evaluation frameworks, and async pipelines.
Real-World Impact
Systems with similar bugs may experience:
- Silent data corruption — items appear missing or reordered.
- Infinite loops when recursion never reaches a base case.
- Performance collapse due to exponential recursion.
- Hard-to-debug nondeterministic behavior when multiple consumers race over the same iterator.
Example or Code (if necessary and relevant)
A minimal reproduction showing iterator exhaustion:
const it = [1,2,3].values();
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 }
Every recursive call to reverseLazy consumes more of the same iterator, so only the last values remain available at deeper levels.
How Senior Engineers Fix It
Experienced engineers typically:
- Avoid recursion over stateful iterators.
- Convert iterators to arrays when reversal is required.
- Clone or buffer input before recursive consumption.
- Define clear ownership of iterator consumption.
- Use well-defined lazy combinators that do not mutate shared state.
A correct lazy reverse requires buffering:
function* reverseLazy(iterable) {
const items = [...iterable];
for (let i = items.length - 1; i >= 0; i--) {
yield items[i];
}
}
Why Juniors Miss It
Common reasons include:
- Misunderstanding the difference between iterables and iterators.
- Assuming recursion “restarts” iteration, not realizing the iterator is shared.
- *Overtrusting `yield`** without tracing control flow.
- Not recognizing that
for...ofmutates iterator state. - Believing generators behave like pure functions, when they actually maintain internal state.
The function “works” by accident, not by design, and the illusion of correctness hides the underlying iterator mutation.