Course contentsShow
← Node.js
Lesson 268 of 1,9803. Asynchronous Patterns and Flow ControlFree lesson

Managing Asynchronous Memory Backpressure

You will implement flow control to pause incoming async tasks when the internal heap approaches limits.

In this lesson, you will learn how to prevent your Node.js application from crashing by pausing incoming data when your system’s memory usage hits a critical limit.

The Memory Overload Problem

When you process high-speed asynchronous data, your application reads information into the "heap" (system memory) faster than it can process or write it out. If the input speed exceeds your processing capacity, these tasks queue up in memory, causing the heap to grow until Node.js hits a limit and crashes with an OutOfMemory error. This is Memory Backpressure.

To solve this, we implement a "throttle" that forces the data source to wait until our heap has "breathed" and cleared some space.

Implementing a Memory-Aware Gate

We can use process.memoryUsage().heapUsed to check our current footprint. If it exceeds a predefined threshold, we pause the data flow.

const threshold = 500 * 1024 * 1024; // 500MB limit

function handleIncomingData(stream) {
  stream.on('data', (chunk) => {
    const memory = process.memoryUsage().heapUsed;

    if (memory > threshold) {
      // Pause the source to allow the garbage collector to work
      stream.pause();
      
      console.log('Memory limit reached. Pausing stream...');
      
      // Wait for memory to drop below a safe level before resuming
      const checkInterval = setInterval(() => {
        if (process.memoryUsage().heapUsed < threshold * 0.8) {
          clearInterval(checkInterval);
          stream.resume();
        }
      }, 1000);
    }
    
    // Process chunk...
  });
}

Why This Works

By calling stream.pause(), you stop the event loop from emitting new data events. This doesn't discard data; it signals to the upstream source (like a file or network socket) that the "pipe" is full. The upstream source buffers the data on its end—or slows down its transmission—preventing your application from consuming more memory than it can handle. Once the garbage collector clears enough heap space, we call stream.resume() to start the flow again.

This simple mechanism acts as a traffic light for your data, ensuring your application stays stable under heavy load without needing to drop requests entirely.

Key Takeaway: Managing memory backpressure means proactively pausing data consumption when the heap exceeds a threshold, allowing the system to catch up before accepting more work.