Event Loop in Node.js Part 1

Aashish Moktan
codingmountain
Published in
4 min readAug 25, 2023

In this article, we are going to see the basics of Event Loop, Event Loop Queues, and simple examples that demonstrate an order of execution in Event Loop.

First of all, what is Event Loop,

An Event Loop is an infinite loop that keeps up and running throughout the program and keeps waiting for an event to trigger. On event trigger, it registers an event into queues and starts executing them once all synchronous tasks are executed. By doing so, Event Loop runs all asynchronous tasks behind the scenes by delegating them to other threads or an operating system which ensures our main thread is not blocked as Node.js is single-threaded.

If there are any completed tasks in queues, the event loop constantly keeps checking for the call stack to become empty, if empty it will push the callback into the call stack and the call stack will execute it immediately. The order in which callback functions are executed depends upon the queues they are registered into as queues have their own priority. To understand

Event Loop Queues

As there are different types of queues, how does Event Loop decide which callback to register into which queue? For that to decide, it checks in which method they are inside. Let’s understand more about it using the below diagram.

As Event Loop keeps up and running in a cycle, it follows a specific order of execution. Let’s understand more with the following points:

  • Whenever an Event Loop starts it checks if there are any callbacks ready to be executed in the Microtask Queue. If there are any, it will push that into the call stack. It is also divided into two types i.e. nextTick Queue and Promise Queue. nextTick Queue has higher priority compared to Promise Queue.
process.nextTick(() => {
console.log("I am registered into nextTick Queue");
});

Promise.resolve().then(()=>{
console.log("I am registered into Promise Queue");
});
  • In the next step, the Event loop will check if there are any callbacks ready to execute in a Timer Queue, if yes it will push that into the call stack. All callbacks inside setTimeout() and setInterval() are registered under the Timer Queue. It has a lower priority compared to the Microtask Queue. Example of a Timer Queue:
setTimeout(() => {
console.log('I am registered into Timer Queue');
}, 0);
  • Next, it will check if there are any callbacks ready in an I/O Queue, if yes it will push that into the call stack. All callbacks related to I/O such as reading/writing a file, and reading/writing from a socket will be registered under it. It has a lower priority compared to the Timer Queue. Example of an I/O Queue:
const file_descriptor = fs.open(__filename, (err, fd) => {
fs.readFile(fd, "utf-8", (readErr, data) => {
console.log("I am registered into I/O Queue");

fs.close(fd, () => {
console.log("I am registered into Close Queue");
});
});
});
  • Next, it will check if any callbacks are ready in a Check Queue, if yes it will push that into the call stack. All callbacks inside of setImmediate() will be registered under it. It has a lower priority compared to the Timer Queue.
setImmediate(() => {
console.log('I am registered into Check Queue');
});
  • Next, it goes to check if any callbacks are ready in a Close Queue, if ready it will push that into the call stack. All callbacks related to closing will be registered under it such as closing a file, closing a socket, and so on.
const file_descriptor = fs.open(__filename, (err, fd) => {
fs.readFile(fd, "utf-8", (readErr, data) => {
fs.close(fd, () => {
console.log("I am registered into Close Queue");
});
});
});

Let’s put all those code together and try to run it to prove what we have discussed:

const fs = require("fs");

process.nextTick(() => {
console.log("I am registered into nextTick Queue");
});

Promise.resolve().then(() => {
console.log("I am registered into Promise Queue");
});

setTimeout(() => {
console.log("I am registered into Timer Queue");
}, 0);

setImmediate(() => {
console.log("I am registered into Check Queue");
});

const file_descriptor = fs.open(__filename, (err, fd) => {
fs.readFile(fd, "utf-8", (readErr, data) => {
console.log("I am registered into I/O Queue");

fs.close(fd, () => {
console.log("I am registered into Close Queue");
});
});
});

The output looks like this:

// Output
I am registered into nextTick Queue
I am registered into Promise Queue
I am registered into Timer Queue
I am registered into Check Queue
I am registered into I/O Queue
I am registered into Close Queue

What we can conclude from the above output is nextTick Queue > Promise Queue > Timer Queue > I/O Queue > Check Queue > Close Queue in terms of priority.

Conclusion

An Event Loop is an infinite loop that waits for an event to trigger and upon triggering the event it registers a callback into the queues, runs them asynchronously in the background, and pushes them into the call stack for execution once they are completed.

I hope this blog has provided valuable insights and added some value to your knowledge portfolio.

Thank you for reading until the end.

Was it helpful?

Don’t forget to like, and comment.

Follow me for more web development tips.

--

--