Course contentsShow
← Node.js
Lesson 1851 of 1,98017. Performance Tuning and ProfilingFree lesson

Understanding Microtask Queue Overflow

You will identify performance degradation caused by recursive promise resolution.

In this lesson, you will learn how recursive promise resolution can starve the Event Loop and degrade your application's performance by overwhelming the microtask queue.

The Microtask Priority Trap

In Node.js, the Event Loop prioritizes the Microtask Queue (containing process.nextTick and Promise callbacks) over the Macrotask Queue (timers, I/O callbacks, and setImmediate). After every operation in the Event Loop, Node.js processes the entire microtask queue before moving on to the next phase.

While this ensures rapid execution of asynchronous resolutions, it creates a dangerous performance pitfall: if your code recursively schedules new microtasks faster than they can be cleared, the Event Loop gets trapped in a cycle of processing the microtask queue. This prevents the loop from ever reaching the next phase, effectively freezing your server.

The Recursive Promise Pattern

Consider an scenario where an asynchronous function recursively calls itself:

function recursivePromise() {
  Promise.resolve().then(() => {
    // If this runs indefinitely, the event loop cannot proceed
    recursivePromise();
  });
}

recursivePromise();

In the example above, each execution adds a new microtask to the queue. Because the microtask queue is exhausted before the loop moves to the next phase, the Event Loop remains blocked. This results in "Event Loop starvation," where incoming HTTP requests are ignored, timers never fire, and the process eventually stops responding to health checks.

Identifying the Degradation

When you encounter a process that shows high CPU usage but zero throughput, you are likely witnessing a microtask overflow. Unlike synchronous blocking (which keeps the stack busy), this blocks the loop infrastructure.

To mitigate this, you must introduce "yield points." If you have a recursive process that handles large datasets, use setImmediate() to force the task into the Macrotask Queue. This allows the Event Loop to perform a full cycle—processing I/O, timers, and pending network requests—before returning to your application logic. This break in the chain gives the runtime the breathing room it needs to maintain high throughput.

Key Takeaway: Recursively resolving promises creates an infinite loop in the microtask queue that prevents the Event Loop from processing I/O, leading to severe performance degradation and application non-responsiveness.