Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

reverseLazy in JavaScript: How Does It Work?

How does reverseLazy reverse an iterable without infinite recursion? Understand this JavaScript trick with iterators and lazy evaluation.
JavaScript reverseLazy function visualized with a spiraling arrow reversing data and a surprised developer, illustrating lazy recursion in JavaScript iterables JavaScript reverseLazy function visualized with a spiraling arrow reversing data and a surprised developer, illustrating lazy recursion in JavaScript iterables
  • 🔁 reverseLazy lets you go through items in reverse right away, without making a reversed copy.
  • ⚙️ It uses recursion and JavaScript generators to give elements in reverse order.
  • 📉 Lazy evaluation saves memory for big or endless data sets.
  • 🚫 Using iterators wrong can cause endless recursion or no output.
  • 🧠 It shows important JavaScript ideas like yield*, iteration protocols, and how the call stack works.

What if you could reverse things in JavaScript without storing a reversed copy? That's what the reverseLazy generator pattern does. It uses recursion and lazy evaluation to reverse items one by one. In this article, you'll look closely at how it works. You'll also find out when and why to use it, and you'll see possible problems. Plus, you will see how it uses the power of iterable rules and JavaScript generators to get good results simply.

What is reverseLazy?

The reverseLazy technique is a generator pattern in JavaScript that uses recursion. It is made to go through an iterable input and give its elements in reverse. It does this without checking everything right away or reversing the whole list in memory. Instead of making a reversed array or set of results, it gives one item at a time as the recursion finishes. This pattern is good when saving memory or changing data right away is very important.

Here’s the core concept for reverseLazy in JavaScript:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

function* reverseLazy(iter) {
    for (const value of iter) {
        yield* reverseLazy(iter);
        yield value;
    }
}

At first, this looks like it will endlessly recurse over the same list. But the key is how the list is handled. This makes sure each recursion gets new elements or saved data that lets it go through everything.

To really get how reverseLazy works, you need to understand two important parts: generators and iterables in JavaScript.

Understanding JavaScript Iterators and Generators

JavaScript provides strong tools to work with lists and data streams using iterators and generators. Understanding how they work is key to understanding reverseLazy.

Iterables

An iterable is any JavaScript object that has the @@iterator method, which gives back an iterator. Arrays, Maps, Sets, and even strings are iterable by default.

const arr = [1, 2, 3];
const iterator = arr[Symbol.iterator]();

console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }

Generators

Generators make custom iteration simpler by handling the state behind the scenes. They are functions defined with the function* syntax and use the yield keyword to make values one at a time.

function* numbers() {
    yield 1;
    yield 2;
    yield 3;
}

for (const num of numbers()) {
    console.log(num); // 1, 2, 3
}

Generators are lazy: they figure out their yield values only when .next() is called. This gives you close control over when and how values are made. It is great for programming that saves memory.

Delegating with yield*

The yield* operator gives control to another generator or iterable. It manages a whole list, giving values as if they were part of the current generator.

function* parent() {
    yield* childGenerator(); // Delegates to child
}

This giving of control is key in how reverseLazy is designed. And it lets you go through iterables using recursion.

How reverseLazy Runs

To reverse an iterable using lazy evaluation and recursion, look at this generator:

function* reverseLazyArray(arr) {
    function* inner(index) {
        if (index >= 0) {
            yield* inner(index - 1); // Recurse deeper
            yield arr[index];        // Yield after recursion
        }
    }
    yield* inner(arr.length - 1);
}

Why This Works

This structure uses an array in reverse by going down with recursion to the start. Then it gives values on the way back up. It does not make a reversed array. The generator lazily gives out values one by one. It keeps the order by using how deep the recursion goes.

For example:

const result = [...reverseLazyArray([1, 2, 3])];
console.log(result); // [3, 2, 1]

This simply reverses the list with very little extra memory use.

Internal Visualization

Here’s how the recursion unfolds:

reverseLazyArray([1, 2, 3])
  --> inner(2)
        --> inner(1)
              --> inner(0)
                    --> inner(-1) ⛔ (base case)
                    <-- yield 1
              <-- yield 2
        <-- yield 3

The sequence finishes from its deepest point, giving elements in reverse. This is what a stack usually does.

The Importance of Iterable Safety

The original reverseLazy version has a common mistake: if you pass the same iterable again and again, the iterator runs out. Once an iterator is used up, you cannot go back or use it again.

Problem Example:

const nums = [1, 2, 3][Symbol.iterator]();
for (const val of nums) {
    console.log(val);  // This will work once
}
for (const val of nums) {
    console.log(val);  // This produces nothing (already consumed)
}

How to Avoid the Problem

To safely use reverseLazy, make sure recursive calls get new inputs:

  • Convert iterables to arrays:
    const buffer = Array.from(iter);
    
  • Use index-based access: You can avoid iterators running out by looping through arrays by their index.

Practical Use Cases of reverseLazy

The ideas behind reverseLazy are not just for learning; they work in real life.

1. Working with Log Streams

Logs can come as a live stream (e.g., WebSocket or server stream). If you want to process logs from newest to oldest without saving the whole stream:

function* reverseStream(logGenerator) {
    const logs = Array.from(logGenerator());
    for (let i = logs.length - 1; i >= 0; i--) {
        yield logs[i];  // Only stores when needed
    }
}

2. Lazy Reversal in Functional Utilities

Libraries that work with iterables (like Ramda, Lodash/fp) can offer reverseLazy tools for lazy changes that can be combined.

3. Reversing Infinite Sequences ⚠️

Although reversing endless lists lazily sounds good, it is usually not possible because you would need to get to the end. But lazy tools like reverseLazy still work with streams that are not limited but do end eventually.

Performance Showdown: Eager vs Lazy Reversal

Let’s benchmark two strategies:

Eager:

const dataset = Array.from({ length: 1_000_000 }, (_, i) => i);
const reversed = [...dataset].reverse();  // Full copy in memory

This is fast but uses a lot of memory, mainly with very big data sets.

Lazy:

function* reverseWithStack(input) {
    const stack = [...input];
    while (stack.length) yield stack.pop();
}

const lazyReversed = [...reverseWithStack(dataset)];

This uses about half the memory as it recycles the original structure.

Conclusion:

  • Use eager reversal for small data and speed.
  • Use lazy reversal for streams, huge datasets, or memory-limited environments.

Alternatives to reverseLazy

If full recursion is too risky (because of the call stack size), here are other ways that act like reverseLazy.

Stack-Based Reversal (No Recursion)

function* reverseWithBuffer(iterable) {
    const stack = [...iterable];
    while (stack.length) yield stack.pop();
}

Using External Libraries

  • Lodash: _.reverse() reverses arrays in-place.
  • RxJS: Offers Observable#reverse() via custom combination operators.
  • Lazy.js: Has lazy recursion and reversal tools already built-in.

Is reverseLazy Safe for Production Use?

While powerful:

✅ It’s great for learning recursion, generators, and lazy design.

⚠️ Use caution where:

  • Stack overflow is a risk.
  • Iterator duplication is needed but not obvious.
  • Performance tuning requires tight control over each step.

A Safer reverseLazy Variant

Let’s make a version that buffers but still uses lazy consumption, and does not use recursion:

function* saferReverseLazy(input) {
    const buffer = Array.from(input);
    for (let i = buffer.length - 1; i >= 0; i--) {
        yield buffer[i];
    }
}

This is not pure recursion, but it does not risk stack overflows or bad behavior from iterators running out.

Educational Value of reverseLazy

From a teaching point of view, reverseLazy is very helpful. Here's why:

  • 🎓 Shows how JavaScript Generators stop and start state
  • 🌀 Shows how recursive memory works and how the stack unwinds
  • 🧮 Helps with functional thinking for iterable changes
  • 🧰 Connects theory (recursion) and use (lazy sequences)

Interview Readiness: Reversing Iterators

This comes up in interviews like:

“Can you reverse an infinite or streaming iterable?”

Your answer:

  • ✔ Yes, if it’s finite and buffered using yield* and recursion.
  • ❌ No, if the stream never ends or does not have a known length.

Being able to look at trade-offs and write reverseLazy from memory shows you really get:

  • Iteration Protocols
  • Lazy Evaluation
  • JavaScript Generators
  • Memory Efficiency

Sample Question Response

“Here’s a generator of values from 1 to 10. Can you write a function that reverses it without loading a reversed copy?”

Boom — Then, write reverseLazy safely using buffering and generator logic to give values.

CodeSandbox and Further Practice

Check out this CodeSandbox demo to try out reverseLazy. You can also test how far deeply nested recursive calls can go and see how well it works compared to eager methods.

Also check out more of our guides:

  • [How JavaScript Generators Work Under the Hood]
  • [Building a Custom Iterable in JavaScript]
  • [Stream Processing with Async Generators]
  • [How to make Recursive Functions better for Performance in JS]

Citations

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading