From Queue to Call Stack: Understanding the Node.js Event Loop

JavaScript is known for being a single threaded, asynchronous runtime. This is what makes it a great fit for high I/O servers, allowing Node to serve large numbers of concurrent connections with little memory or CPU overhead. But how does it really work?

Programs have two fundamental memory regions:

The call stack being LIFO means functions at the top of the stack are returned first. The last item put in, is the first out. …but there’s more to it. We’re going to talk about how callbacks move from the event loop’s queues onto the call stack, where they’re executed.

Microtasks (The high priority VIPs)

First you have the VIP microtasks queue, comprised of two subqueues.

  1. nextTick
  2. Promise callbacks and queueMicrotask

The microtasks queue is drained (meaning every item in the queue is executed) between each phase. In both the timers and the check phases, it also drains after each item in the queue.

For this same reason, recursive calls to nextTick can “starve” the loop. If nextTick callbacks are continually added to the microtasks queue, the queue will never become to allow the loop to move on to the next phase.

The Event Loop

JavaScript is single-threaded, all tasks are managed by the event loop. In Node the event loop is orchestrated by the libuv library. It powers the asynchronous, non-blocking I/O model of Node, managing asynchronous function calls and interacting with the operating system’s APIs.

There are 6 phases to the event loop:

The 6 phases

Node.js event loop execution points

Thou should beware of nextTick

When you call nextTick all callbacks passed to it will execute before the loop resumes. Recursive calls to nextTick can “starve” the I/O, preventing the event loop from returning to the I/O phase.

P.S: Mentally reverse nextTick and setImmediate. Despite the name, nextTick is the more “immediate” of the two. nextTick fires before the next phase, whereas setImmediate runs on the next check phase of the loop.