Node.js uses a single-threaded event loop to handle asynchronous operations. This means that all the JavaScript code is executed on a single thread, but the event loop can handle multiple operations concurrently.

The JavaScript code consists of two lines of execution:

  • The mainline: This is the JavaScript that runs when Node first runs your program. It runs from start to finish, and when it is finished, it gives up control to the event loop.
  • The event loop: This is where all of your callbacks are run.

The following diagram shows a simplified overview of the event loop's order of operations.

    ┌───────────────────────────┐
┌─>│           timers          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │
   └───────────────────────────┘

The event loop in Node.js does not have a predefined number of iterations; it continues running indefinitely until there are no more tasks to perform. Each iteration of the event loop is referred to as a tick. The event loop will keep ticking as long as there are pending operations, such as:

  • Promises and other microtasks.
  • Pending timers (callbacks scheduled with setTimeout or setInterval).
  • Pending I/O operations (e.g., file system operations, network requests).
  • Pending callbacks (e.g., deferred I/O callbacks).
  • setImmediate callbacks.
  • Close callbacks.
Microtasks

All microtasks are placed into a microtask queue, designed specifically for handling them.

The microtask queue is processed and emptied before every phase iteration of the event loop.

Microtasks are executed at specific points in the event loop:

  1. After executing JavaScript code: When the call stack becomes empty, the event loop checks for and executes any pending microtasks before moving to the next phase.
  2. After processing each phase of the event loop: Once the current phase of the event loop is completed, before moving to the next phase, the event loop processes all the pending microtasks.

Here are the main types of microtasks:

  • Promise Callbacks: .then(), .catch(), and .finally() handlers.
  • queueMicrotask: Both queueMicrotask and Promise Callbacks added to the microtask queue and have similar priority.
  • process.nextTick: Callbacks scheduled with process.nextTick are executed before promise callbacks or queueMicrotask callbacks. process.nextTick callbacks are added to the beginning of the microtask queue.

Here's an example:

console.log('Start');
process.nextTick(console.log, "nextTick 1");
Promise.resolve("Promise 1").then(console.log);
queueMicrotask(() => console.log("queueMicrotask 1"));
Promise.reject("Promise 2").catch(console.log);
queueMicrotask(() => console.log("queueMicrotask 2"));
process.nextTick(console.log, "nextTick 2");
console.log('End');

The output would be:

Start
End
nextTick 1
nextTick 2
Promise 1
queueMicrotask 1
Promise 2
queueMicrotask 2

The execution flow is as follows:

  • The mainline
    • The main thread starts by creating the global execution context and pushes it onto the call stack.
    • The main thread executes console.log function call, a function execution context for console.log is added to the call stack.
    • The main thread logs "Start" to the console.
    • The main thread removes function execution context for console.log from the call stack.
    • The main thread executes process.nextTick function call, a function execution context for process.nextTick is added to the call stack.
    • The main thread moves the process.nextTick callback function to the next tick queue.
    • The main thread removes the function execution context for process.nextTick from the call stack.
    • The main thread executes Promise constructor call, and its execution context is created and pushed onto the call stack.
    • The executor function within the Promise constructor initializes the promise.
    • The main thread executes executor function call, and its execution context is created and pushed onto the call stack.
    • The main thread executes resolve function call, and its execution context is created and pushed onto the call stack.
    • Calling resolve transitions the promise from the pending state to the fulfilled (or resolved) state.
    • The value "Promise 1" is set as the value of the new promise internally.
    • The main thread removes function execution context for resolve from the call stack.
    • The main thread removes function execution context for executor function from the call stack.
    • The Promise constructor completes, and its execution context is popped off the call stack.
    • The main thread executes .then() method call, and its execution context is created and pushed onto the call stack.
    • The main thread moves the resolve callback function to the microtask queue (or job queue).
    • The main thread removes the execution context for then() from the call stack.
    • The main thread executes queueMicrotask function call, a function execution context for queueMicrotask is added to the call stack.
    • The main thread moves the queueMicrotask callback function to the microtask queue.
    • The main thread removes the function execution context for queueMicrotask from the call stack.
    • The main thread executes the second Promise constructor call, and its execution context is created and pushed onto the call stack.
    • The executor function within the second Promise constructor initializes the promise.
    • The main thread executes executor function call, and its execution context is created and pushed onto the call stack.
    • The main thread executes reject function call, and its execution context is created and pushed onto the call stack.
    • Calling resolve transitions the promise from the pending state to the rejected state.
    • The value "Promise 2" is set as the value of the new promise internally.
    • The main thread removes function execution context for reject from the call stack.
    • The main thread removes function execution context for executor function from the call stack.
    • The second Promise constructor completes, and its execution context is popped off the call stack.
    • The main thread executes .then() method call, and its execution context is created and pushed onto the call stack.
    • The main thread moves the resolve callback function to the microtask queue (or job queue).
    • The main thread removes the execution context for then() from the call stack.
    • The main thread executes the second queueMicrotask function call, a function execution context for the second queueMicrotask is added to the call stack.
    • The main thread moves the second queueMicrotask callback function to the microtask queue.
    • The main thread removes the function execution context for the second queueMicrotask from the call stack.
    • The main thread executes the second process.nextTick function call, a function execution context for the second process.nextTick is added to the call stack.
    • The main thread moves the second process.nextTick callback function to the next tick queue.
    • The main thread removes the function execution context for the second process.nextTick from the call stack.
    • The main thread executes console.log function call, a function execution context for console.log is added to the call stack.
    • The main thread logs "End" to the console.
    • The main thread removes function execution context for console.log from the call stack.
    • Once all the synchronous code has executed, the main thread removes the global execution context from the call stack.
    • The call stack is now empty, waiting for asynchronous callbacks to be executed.
  • The event loop starts processing the asynchronous callbacks that have been registered.
  • The first loop
    • The first loop starts by running the microtasks queue. The nextTick queue gets priority over the microtask queue.
      • The event loop pushes the task from the nextTick queue onto the call stack and creates a new function execution context for the first process.nextTick callback function.
      • The event loop executes console.log function call, a function execution context for console.log is added to the call stack.
      • The main thread logs "nextTick 1" to the console.
      • The main thread removes function execution context for console.log from the call stack.
      • The main thread removes the function execution context for the first process.nextTick callback function from the call stack.
      • The event loop pushes the second task from the nextTick queue onto the call stack and creates a new function execution context for the second process.nextTick callback function.
      • The main thread executes console.log function call, a function execution context for console.log is added to the call stack.
      • The main thread logs "nextTick 2" to the console.
      • The main thread removes function execution context for console.log from the call stack.
      • The main thread removes the function execution context for the second process.nextTick callback function from the call stack.
      • The event loop pushes the task from the microtask queue onto the call stack and creates a new function execution context for promise then() callback function.
      • The main thread executes console.log function call, a function execution context for console.log is added to the call stack.
      • The main thread logs "Promise 1" to the console.
      • The main thread removes function execution context for console.log from the call stack.
      • The main thread removes the function execution context for promise then() callback function from the call stack.
      • The event loop pushes the next task from the microtask queue onto the call stack and creates a new function execution context for queueMicrotask callback function.
      • The main thread executes console.log function call, a function execution context for console.log is added to the call stack.
      • The main thread logs "queueMicrotask 1" to the console.
      • The main thread removes function execution context for console.log from the call stack.
      • The main thread removes the function execution context for queueMicrotask callback function from the call stack.
      • The event loop pushes the next task from the microtask queue onto the call stack and creates a new function execution context for promise then() callback function.
      • The main thread executes console.log function call, a function execution context for console.log is added to the call stack.
      • The main thread logs "Promise 2" to the console.
      • The main thread removes function execution context for console.log from the call stack.
      • The main thread removes the function execution context for promise then() callback function from the call stack.
      • The event loop pushes the next task from the microtask queue onto the call stack and creates a new function execution context for the second queueMicrotask callback function.
      • The main thread executes console.log function call, a function execution context for console.log is added to the call stack.
      • The main thread logs "queueMicrotask 2" to the console.
      • The main thread removes function execution context for console.log from the call stack.
      • The main thread removes the function execution context for the second queueMicrotask callback function from the call stack.
    • Timers Phase
      • No timers have expired yet (since less than 500 milliseconds have passed).
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Pending Callbacks Phase
      • No pending callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Poll Phase
      • No I/O events to process.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Check Phase
      • No check callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Close Callbacks Phase
      • No close callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
Timers

The first phase of the event loop is the Timers phase. This phase deals with timer callbacks that have reached their scheduled time. Timers in Node.js can be used to schedule code execution after a specified delay or at specific intervals. When a timer's time comes, its callback function is added to the event queue (task queue) to be executed.

Timers are not guaranteed to execute exactly at their scheduled time, as they are subject to the availability of the system and the event loop. For example, if the event loop is busy processing other events, the timer callback may be delayed until the next iteration of the event loop. Therefore, timers should not be used for precise timing, but rather for approximate timing.

Here's an example:

console.log('Start');

setTimeout(() => {
    console.log('Timeout callback 1');
}, 1000);

setTimeout(() => {
    console.log('Timeout callback 2');
}, 500);

process.nextTick(console.log, "nextTick callback");

queueMicrotask(() => console.log("queueMicrotask callback"));

console.log('End');

The output would be:

Start
End
nextTick callback
queueMicrotask callback
Timeout callback 2
Timeout callback 1

The execution flow is as follows:

  • The mainline
    • The main thread starts by creating the global execution context and pushes it onto the call stack.
    • The main thread executes console.log function call, a function execution context for console.log is added to the call stack.
    • The main thread logs "Start" to the console.
    • The main thread removes function execution context for console.log from the call stack.
    • The main thread executes setTimeout function call, a function execution context for setTimeout is added to the call stack.
    • The main thread adds the setTimeout callback function to the timer queue.
    • The event loop continuously checks the timer queue. When the specified delay has elapsed, the callback function is moved to the appropriate phase of the event loop.
    • The main thread removes the function execution context for setTimeout from the call stack.
    • The main thread executes the second setTimeout function call, a function execution context for the second setTimeout is added to the call stack.
    • The main thread adds the second setTimeout callback function to the timer queue.
    • The event loop continuously checks the timer queue. When the specified delay has elapsed, the callback function is moved to the appropriate phase of the event loop.
    • The main thread removes the function execution context for the second setTimeout from the call stack.
    • The main thread executes process.nextTick function call, a function execution context for process.nextTick is added to the call stack.
    • The main thread moves the process.nextTick callback function to the next tick queue.
    • The main thread removes the function execution context for process.nextTick from the call stack.
    • The main thread executes queueMicrotask function call, a function execution context for queueMicrotask is added to the call stack.
    • The main thread moves the queueMicrotask callback function to the microtask queue.
    • The main thread removes the function execution context for queueMicrotask from the call stack.
    • The main thread executes console.log function call, a function execution context for console.log is added to the call stack.
    • The main thread logs "End" to the console.
    • The main thread removes function execution context for console.log from the call stack.
    • Once all the synchronous code has executed, the main thread removes the global execution context from the call stack.
    • The call stack is now empty, waiting for asynchronous callbacks to be executed.
  • The event loop starts processing the asynchronous callbacks that have been registered.
  • The first loop
    • The first loop starts by running the microtasks queue. The nextTick queue gets priority over the microtask queue.
      • The event loop pushes the task from the nextTick queue onto the call stack and creates a new function execution context for process.nextTick callback function.
      • The main thread executes console.log function call, a function execution context for console.log is added to the call stack.
      • The main thread logs "nextTick callback" to the console.
      • The main thread removes function execution context for console.log from the call stack.
      • The main thread removes the function execution context for process.nextTick callback function from the call stack.
      • The event loop pushes the task from the microtask queue onto the call stack and creates a new function execution context for queueMicrotask callback function.
      • The main thread executes console.log function call, a function execution context for console.log is added to the call stack.
      • The main thread logs "queueMicrotask callback" to the console.
      • The main thread removes function execution context for console.log from the call stack.
      • The main thread removes the function execution context for queueMicrotask callback function from the call stack.
    • Timers Phase
      • No timers have expired yet (since less than 500 milliseconds have passed).
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Pending Callbacks Phase
      • No pending callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Poll Phase
      • No I/O events to process.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Check Phase
      • No check callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Close Callbacks Phase
      • No close callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
  • The event loop will continue to loop.
  • The next loop
    • The first loop starts by running the microtasks queue.
      • No microtasks to execute.
    • Timers Phase (After 500 milliseconds)
      • The event loop pushes the task from the callback queue onto the call stack and creates a new function execution context for the second setTimeout callback function.
      • The main thread executes console.log function call, a function execution context for console.log is added to the call stack.
      • The main thread logs "Timeout callback 2" to the console.
      • The main thread removes function execution context for console.log from the call stack.
      • The main thread removes the function execution context for setTimeout callback function from the call stack.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Pending Callbacks Phase
      • No pending callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Poll Phase
      • No I/O events to process.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Check Phase
      • No check callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Close Callbacks Phase
      • No close callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
  • The event loop will continue to loop.
  • The next loop
    • The first loop starts by running the microtasks queue.
      • No microtasks to execute.
    • Timers Phase (After 1000 milliseconds)
      • The event loop pushes the task from the callback queue onto the call stack and creates a new function execution context for the first setTimeout callback function.
      • The event loop executes console.log function call, a function execution context for console.log is added to the call stack.
      • The event loop logs "Timeout callback 1" to the console.
      • The event loop removes function execution context for console.log from the call stack.
      • The event loop removes the function execution context for setTimeout callback function from the call stack.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Pending Callbacks Phase
      • No pending callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Poll Phase
      • No I/O events to process.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Check Phase
      • No check callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Close Callbacks Phase
      • No close callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
Pending Callbacks

I/O operations execute in the poll phase of the event loop. During the poll phase, some specific I/O operations callbacks defer to the pending phase of the next iteration of the event loop. I/O operations callbacks deferred from the previous iteration run in the pending callbacks phase.

Here's an example:

console.log('Start');

const fs = require("fs");

fs.readFile(__filename, (err, data) => {
    if (err) throw err;
    console.log("Pending callback");
});
            
setTimeout(() => {
    console.log('Timeout callback');
}, 0);

process.nextTick(console.log, "nextTick callback");

queueMicrotask(() => console.log("queueMicrotask callback"));

console.log('End');

The output would be:

Start
End
nextTick callback
queueMicrotask callback
Timeout callback
Pending callback

The execution flow is as follows:

  • The mainline
    • The main thread starts by creating the global execution context and pushes it onto the call stack.
    • The main thread executes console.log function call, a function execution context for console.log is added to the call stack.
    • The main thread logs "Start" to the console.
    • The main thread removes function execution context for console.log from the call stack.
    • The main thread executes require function call, a function execution context for require is added to the call stack.
    • The main thread starts resolving the module identifier fs.
    • The main thread wraps the contents of fs in a function and executes it, a function execution context for fs is added to the call stack.
    • The module.exports object is returned and assigned to fs.
    • The main thread removes function execution context for fs from the call stack.
    • The main thread removes function execution context for require from the call stack.
    • The main thread executes fs.readFile function call, a function execution context for fs.readFile is added to the call stack.
    • The main thread initiates a non-blocking file system operation to read the current file.
    • The main thread removes the function execution context for the first fs.readFile from the call stack.
    • The main thread executes setTimeout function call, a function execution context for setTimeout is added to the call stack.
    • The main thread adds the setTimeout callback function to the timer queue.
    • The event loop continuously checks the timer queue. When the specified delay has elapsed, the callback function is moved to the appropriate phase of the event loop.
    • The main thread removes the function execution context for setTimeout from the call stack.
    • The main thread executes process.nextTick function call, a function execution context for process.nextTick is added to the call stack.
    • The main thread moves the process.nextTick callback function to the next tick queue.
    • The main thread removes the function execution context for process.nextTick from the call stack.
    • The main thread executes queueMicrotask function call, a function execution context for queueMicrotask is added to the call stack.
    • The main thread moves the queueMicrotask callback function to the microtask queue.
    • The main thread removes the function execution context for queueMicrotask from the call stack.
    • The main thread executes console.log function call, a function execution context for console.log is added to the call stack.
    • The main thread logs "End" to the console.
    • The main thread removes function execution context for console.log from the call stack.
    • Once all the synchronous code has executed, the main thread removes the global execution context from the call stack.
    • The call stack is now empty, waiting for asynchronous callbacks to be executed.
  • The event loop starts processing the asynchronous callbacks that have been registered.
  • The first loop
    • The first loop starts by running the microtasks queue. The nextTick queue gets priority over the microtask queue.
      • The event loop pushes the task from the nextTick queue onto the call stack and creates a new function execution context for process.nextTick callback function.
      • The event loop executes console.log function call, a function execution context for console.log is added to the call stack.
      • The event loop logs "nextTick callback" to the console.
      • The event loop removes function execution context for console.log from the call stack.
      • The event loop removes the function execution context for process.nextTick callback function from the call stack.
      • The event loop pushes the task from the microtask queue onto the call stack and creates a new function execution context for queueMicrotask callback function.
      • The event loop executes console.log function call, a function execution context for console.log is added to the call stack.
      • The event loop logs "queueMicrotask callback" to the console.
      • The event loop removes function execution context for console.log from the call stack.
      • The event loop removes the function execution context for queueMicrotask callback function from the call stack.
    • Timers Phase
      • The event loop pushes the task from the timer queue onto the call stack and creates a new function execution context for setTimeout callback function.
      • The event loop executes console.log function call, a function execution context for console.log is added to the call stack.
      • The event loop logs "Timeout callback" to the console.
      • The event loop removes function execution context for console.log from the call stack.
      • The event loop removes the function execution context for setTimeout callback function from the call stack.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Pending Callbacks Phase
      • The file reading process is finished, but its callback is not yet marked to be executed because IO callbacks get queued up only at the IO Poll Phase.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Poll Phase
      • The fs.readFile callback event is collected and added to the I/O queue.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Check Phase
      • No check callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Close Callbacks Phase
      • No close callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
  • The event loop will continue to loop.
  • The next loop
    • The first loop starts by running the microtasks queue.
      • No microtasks to execute.
    • Timers Phase
      • No timeout callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Pending Callbacks Phase
      • The event loop pushes the task from the I/O queue onto the call stack and creates a new function execution context for fs.readFile callback function.
      • The event loop executes console.log function call, a function execution context for console.log is added to the call stack.
      • The event loop logs "Pending callback" to the console.
      • The event loop removes function execution context for console.log from the call stack.
      • The event loop removes the function execution context for fs.readFile callback function from the call stack.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Poll Phase
      • No I/O events to process.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Check Phase
      • No check callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Close Callbacks Phase
      • No close callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
Idle Phase

The idle phase is not a normal phase of the Node.js event loop. It is a period whereby the event loop has nothing to do but perform background tasks like checking for low-priority results or running garbage collection.

Poll Phase

The poll phase is where I/O operations execute. I/O operations transfer data to or from a computer. The event loop checks for new I/O operations and executes them in the poll queue.

Here's an example:

console.log('Start');

const fs = require("fs");

fs.readFile(__filename, (err, data) => {
    if (err) throw err;
    console.log("Pending callback");
});

const http = require("http");

http.get("http://localhost", (res) => {
    console.log("Poll callback");
    res.on("data", (chunk) => {
        // Do something with the data
        console.log("Data event listener callback");
    });
});

setTimeout(() => {
    console.log('Timeout callback');
}, 0);

process.nextTick(console.log, "nextTick callback");

queueMicrotask(() => console.log("queueMicrotask callback"));

console.log('End');

The output would be:

Start
End
nextTick callback
queueMicrotask callback
Timeout callback
Poll callback
Data event listener callback
Pending callback

The execution flow is as follows:

  • The mainline
    • The main thread starts by creating the global execution context and pushes it onto the call stack.
    • The main thread executes console.log function call, a function execution context for console.log is added to the call stack.
    • The main thread logs "Start" to the console.
    • The main thread removes function execution context for console.log from the call stack.
    • The main thread executes require function call, a function execution context for require is added to the call stack.
    • The main thread starts resolving the module identifier fs.
    • The main thread wraps the contents of fs in a function and executes it, a function execution context for fs is added to the call stack.
    • The module.exports object is returned and assigned to fs.
    • The main thread removes function execution context for fs from the call stack.
    • The main thread removes function execution context for require from the call stack.
    • The main thread executes fs.readFile function call, a function execution context for fs.readFile is added to the call stack.
    • The main thread initiates a non-blocking file system operation to read the current file.
    • The main thread moves the fs.readFile callback function to the I/O queue.
    • The main thread removes the function execution context for the first fs.readFile from the call stack.
    • The main thread executes require function call, a function execution context for require is added to the call stack.
    • The main thread starts resolving the module identifier http.
    • The main thread wraps the contents of http in a function and executes it, a function execution context for http is added to the call stack.
    • The module.exports object is returned and assigned to http.
    • The main thread removes function execution context for http from the call stack.
    • The main thread removes function execution context for require from the call stack.
    • The main thread executes http.get function call, a function execution context for http.get is added to the call stack.
    • The main thread initiates initiates an HTTP GET request.
    • The main thread moves the http.get callback function to the I/O queue.
    • The main thread removes the function execution context for the first http.get from the call stack.
    • The main thread executes setTimeout function call, a function execution context for setTimeout is added to the call stack.
    • The main thread adds the setTimeout callback function to the timer queue.
    • The event loop continuously checks the timer queue. When the specified delay has elapsed, the callback function is moved to the appropriate phase of the event loop.
    • The main thread removes the function execution context for setTimeout from the call stack.
    • The main thread executes process.nextTick function call, a function execution context for process.nextTick is added to the call stack.
    • The main thread moves the process.nextTick callback function to the next tick queue.
    • The main thread removes the function execution context for process.nextTick from the call stack.
    • The main thread executes queueMicrotask function call, a function execution context for queueMicrotask is added to the call stack.
    • The main thread moves the queueMicrotask callback function to the microtask queue.
    • The main thread removes the function execution context for queueMicrotask from the call stack.
    • The main thread executes console.log function call, a function execution context for console.log is added to the call stack.
    • The main thread logs "End" to the console.
    • The main thread removes function execution context for console.log from the call stack.
    • Once all the synchronous code has executed, the main thread removes the global execution context from the call stack.
    • The call stack is now empty, waiting for asynchronous callbacks to be executed.
  • The event loop starts processing the asynchronous callbacks that have been registered.
  • The first loop
    • The first loop starts by running the microtasks queue. The nextTick queue gets priority over the microtask queue.
      • The event loop pushes the task from the nextTick queue onto the call stack and creates a new function execution context for process.nextTick callback function.
      • The event loop executes console.log function call, a function execution context for console.log is added to the call stack.
      • The event loop logs "nextTick callback" to the console.
      • The event loop removes function execution context for console.log from the call stack.
      • The event loop removes the function execution context for process.nextTick callback function from the call stack.
      • The event loop pushes the task from the microtask queue onto the call stack and creates a new function execution context for queueMicrotask callback function.
      • The event loop executes console.log function call, a function execution context for console.log is added to the call stack.
      • The event loop logs "queueMicrotask callback" to the console.
      • The event loop removes function execution context for console.log from the call stack.
      • The callback function has no more code to run.
      • The event loop removes the function execution context for queueMicrotask callback function from the call stack.
    • Timers Phase
      • The event loop pushes the task from the timer queue onto the call stack and creates a new function execution context for setTimeout callback function.
      • The event loop executes console.log function call, a function execution context for console.log is added to the call stack.
      • The event loop logs "Timeout callback" to the console.
      • The event loop removes function execution context for console.log from the call stack.
      • The callback function has no more code to run.
      • The event loop removes the function execution context for setTimeout callback function from the call stack.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Pending Callbacks Phase
      • No pending callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Poll Phase
      • The file reading process isn't finished.
      • The HTTP request is sent over the network and waits for the server response.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Check Phase
      • No check callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Close Callbacks Phase
      • No close callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
  • The event loop will continue to loop.
  • The next loop
    • The first loop starts by running the microtasks queue.
      • No microtasks to execute.
    • Timers Phase
      • No timeout callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Pending Callbacks Phase
      • The file reading process is finished, but its callback is not yet marked to be executed because IO callbacks get queued up only at the IO Poll Phase.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Poll Phase
      • The fs.readFile callback event is collected and added to the I/O queue.
      • When the response headers from the server are received, the event loop pushes the task from the I/O queue onto the call stack and creates a new function execution context for http.get callback function.
      • The server localhost responds HTTP GET request, the data starts arriving in chunks.
      • The event loop executes the data event listener call, a function execution context for data event listener is added to the call stack.
      • The event loop moves the calbback function for data event listener to the event queue (task queue).
      • Each chunk of data triggers a data event on the response object.
      • Once the event loop detects the data event, the event loop will move the callback function for data event listener from the event queue (task queue) to the call stack to be executed and create a new function execution context for callback function.
      • The event loop executes console.log function call, a function execution context for console.log is added to the call stack.
      • The event loop logs "Data event listener callback" to the console.
      • The event loop removes function execution context for console.log from the call stack.
      • The callback function has no more code to run.
      • The event loop removes function execution context for callback function from the call stack.
      • The data event listener also has no more code to run.
      • The event loop removes function execution context for data event listener from the call stack.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Check Phase
      • No check callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Close Callbacks Phase
      • No close callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
  • The event loop will continue to loop.
  • The next loop
    • The first loop starts by running the microtasks queue.
      • No microtasks to execute.
    • Timers Phase
      • No timeout callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Pending Callbacks Phase
      • The event loop pushes the task from the I/O queue onto the call stack and creates a new function execution context for fs.readFile callback function.
      • The event loop executes console.log function call, a function execution context for console.log is added to the call stack.
      • The event loop logs "Pending callback" to the console.
      • The event loop removes function execution context for console.log from the call stack.
      • The callback function has no more code to run.
      • The event loop removes the function execution context for fs.readFile callback function from the call stack.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Poll Phase
      • No I/O events to process.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Check Phase
      • No check callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Close Callbacks Phase
      • No close callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
Check Phase

The check phase is where the setImmediate timer runs. The Node.js event loop goes to the check phase when there is a setImmediate in the program, and the poll phase becomes idle or when the poll phase completes.

Here's an example:

console.log('Start');

const fs = require("fs");

fs.readFile(__filename, (err, data) => {
    if (err) throw err;
    console.log("Pending callback");
});

const http = require("http");

http.get("http://localhost", (res) => {
    console.log("Poll callback");
    res.on("data", (chunk) => {
        // Do something with the data
        console.log("Data event listener callback");
    });
});

setTimeout(() => {
    console.log('Timeout callback');
}, 0);

setImmediate(() => {
    console.log("Check callback");
});

process.nextTick(console.log, "nextTick callback");

queueMicrotask(() => console.log("queueMicrotask callback"));

console.log('End');

The output would be:

Start
End
nextTick callback
queueMicrotask callback
Timeout callback
Check callback
Poll callback
Data event listener callback
Pending callback

The execution flow is as follows:

  • The mainline
    • The main thread starts by creating the global execution context and pushes it onto the call stack.
    • The main thread executes console.log function call, a function execution context for console.log is added to the call stack.
    • The main thread logs "Start" to the console.
    • The main thread removes function execution context for console.log from the call stack.
    • The main thread executes require function call, a function execution context for require is added to the call stack.
    • The main thread starts resolving the module identifier fs.
    • The main thread wraps the contents of fs in a function and executes it, a function execution context for fs is added to the call stack.
    • The module.exports object is returned and assigned to fs.
    • The main thread removes function execution context for fs from the call stack.
    • The main thread removes function execution context for require from the call stack.
    • The main thread executes fs.readFile function call, a function execution context for fs.readFile is added to the call stack.
    • The main thread initiates a non-blocking file system operation to read the current file.
    • The main thread moves the fs.readFile callback function to the I/O queue.
    • The main thread removes the function execution context for the first fs.readFile from the call stack.
    • The main thread executes require function call, a function execution context for require is added to the call stack.
    • The main thread starts resolving the module identifier http.
    • The main thread wraps the contents of http in a function and executes it, a function execution context for http is added to the call stack.
    • The module.exports object is returned and assigned to http.
    • The main thread removes function execution context for http from the call stack.
    • The main thread removes function execution context for require from the call stack.
    • The main thread executes http.get function call, a function execution context for http.get is added to the call stack.
    • The main thread initiates initiates an HTTP GET request.
    • The main thread moves the http.get callback function to the I/O queue.
    • The main thread removes the function execution context for the first http.get from the call stack.
    • The main thread executes setTimeout function call, a function execution context for setTimeout is added to the call stack.
    • The main thread adds the setTimeout callback function to the timer queue.
    • The event loop continuously checks the timer queue. When the specified delay has elapsed, the callback function is moved to the appropriate phase of the event loop.
    • The main thread removes the function execution context for setTimeout from the call stack.
    • The main thread executes setImmediate function call, a function execution context for console.log is added to the call stack.
    • The main thread moves the setImmediate callback function to the check queue.
    • The main thread removes function execution context for setImmediate from the call stack.
    • The main thread executes process.nextTick function call, a function execution context for process.nextTick is added to the call stack.
    • The main thread moves the process.nextTick callback function to the next tick queue.
    • The main thread removes the function execution context for process.nextTick from the call stack.
    • The main thread executes queueMicrotask function call, a function execution context for queueMicrotask is added to the call stack.
    • The main thread moves the queueMicrotask callback function to the microtask queue.
    • The main thread removes the function execution context for queueMicrotask from the call stack.
    • The main thread executes console.log function call, a function execution context for console.log is added to the call stack.
    • The main thread logs "End" to the console.
    • The main thread removes function execution context for console.log from the call stack.
    • Once all the synchronous code has executed, the main thread removes the global execution context from the call stack.
    • The call stack is now empty, waiting for asynchronous callbacks to be executed.
  • The event loop starts processing the asynchronous callbacks that have been registered.
  • The first loop
    • The first loop starts by running the microtasks queue. The nextTick queue gets priority over the microtask queue.
      • The event loop pushes the task from the nextTick queue onto the call stack and creates a new function execution context for process.nextTick callback function.
      • The event loop executes console.log function call, a function execution context for console.log is added to the call stack.
      • The event loop logs "nextTick callback" to the console.
      • The event loop removes function execution context for console.log from the call stack.
      • The event loop removes the function execution context for process.nextTick callback function from the call stack.
      • The event loop pushes the task from the microtask queue onto the call stack and creates a new function execution context for queueMicrotask callback function.
      • The event loop executes console.log function call, a function execution context for console.log is added to the call stack.
      • The event loop logs "queueMicrotask callback" to the console.
      • The event loop removes function execution context for console.log from the call stack.
      • The callback function has no more code to run.
      • The event loop removes the function execution context for queueMicrotask callback function from the call stack.
    • Timers Phase
      • The event loop pushes the task from the timer queue onto the call stack and creates a new function execution context for setTimeout callback function.
      • The event loop executes console.log function call, a function execution context for console.log is added to the call stack.
      • The event loop logs "Timeout callback" to the console.
      • The event loop removes function execution context for console.log from the call stack.
      • The callback function has no more code to run.
      • The event loop removes the function execution context for setTimeout callback function from the call stack.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Pending Callbacks Phase
      • No pending callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Poll Phase
      • The file reading operation isn't finished.
      • The HTTP request is sent over the network and waits for the server response.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Check Phase
      • The event loop pushes the task from the check queue onto the call stack and creates a new function execution context for setImmediate callback function.
      • The event loop executes console.log function call, a function execution context for console.log is added to the call stack.
      • The event loop logs "Check callback" to the console.
      • The event loop removes function execution context for console.log from the call stack.
      • The callback function has no more code to run.
      • The event loop removes the function execution context for setImmediate callback function from the call stack.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Close Callbacks Phase
      • No close callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
  • The event loop will continue to loop.
  • The next loop
    • The first loop starts by running the microtasks queue.
      • No microtasks to execute.
    • Timers Phase
      • No timeout callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Pending Callbacks Phase
      • The file reading operation is finished, but its callback is not yet marked to be executed because IO callbacks get queued up only at the IO Poll Phase.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Poll Phase
      • The fs.readFile callback event is collected and added to the I/O queue.
      • When the response headers from the server are received, the event loop pushes the task from the I/O queue onto the call stack and creates a new function execution context for http.get callback function.
      • The server localhost responds HTTP GET request, the data starts arriving in chunks.
      • The event loop executes the data event listener call, a function execution context for data event listener is added to the call stack.
      • The event loop moves the calbback function for data event listener to the event queue (task queue).
      • Each chunk of data triggers a data event on the response object.
      • Once the event loop detects the data event, the event loop will move the callback function for data event listener from the event queue (task queue) to the call stack to be executed and create a new function execution context for callback function.
      • The event loop executes console.log function call, a function execution context for console.log is added to the call stack.
      • The event loop logs "Data event listener callback" to the console.
      • The event loop removes function execution context for console.log from the call stack.
      • The callback function has no more code to run.
      • The event loop removes function execution context for callback function from the call stack.
      • The data event listener also has no more code to run.
      • The event loop removes function execution context for data event listener from the call stack.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Check Phase
      • No check callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Close Callbacks Phase
      • No close callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
  • The event loop will continue to loop.
  • The next loop
    • The first loop starts by running the microtasks queue.
      • No microtasks to execute.
    • Timers Phase
      • No timeout callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Pending Callbacks Phase
      • The event loop pushes the task from the I/O queue onto the call stack and creates a new function execution context for fs.readFile callback function.
      • The event loop executes console.log function call, a function execution context for console.log is added to the call stack.
      • The event loop logs "Pending callback" to the console.
      • The event loop removes function execution context for console.log from the call stack.
      • The callback function has no more code to run.
      • The event loop removes the function execution context for fs.readFile callback function from the call stack.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Poll Phase
      • No I/O events to process.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Check Phase
      • No check callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Close Callbacks Phase
      • No close callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
Close Callbacks Phase

The close callbacks phase is the last phase of the Node.js event loop. The close callback phase is where callbacks from the close event of a socket and the closing of an HTTP server run.

The close callbacks phase is designed to handle cleanup operations related to resource closure, such as the closing of TCP connections, file descriptors, and other resources.

Here's an example:

console.log('Start');

const fs = require('fs');

const readable = fs.createReadStream(__filename);

readable.on('data', (chunk) => {
    console.log('Data event listener');
});

readable.on('close', () => {
    console.log('Close callback');
});

const http = require("http");

http.get("http://localhost", (res) => {
    console.log("Poll callback");
    res.on("data", (chunk) => {
        // Do something with the data
        console.log("Data event listener callback");
    });
});

setTimeout(() => {
    console.log('Timeout callback');
}, 0);

setImmediate(() => {
    console.log("Check callback");
});

process.nextTick(console.log, "nextTick callback");

queueMicrotask(() => console.log("queueMicrotask callback"));

console.log('End');

The output depends on which operation finishes first, the HTTP request or the file reading operation.

The output would be:

Start
End
nextTick callback
queueMicrotask callback
Timeout callback
Check callback
Poll callback
Data event listener callback
Pending callback

Or:

Start
End
nextTick callback
queueMicrotask callback
Timeout callback
Check callback
Data event listener
Poll callback
Data event listener callback
Close callback

The execution flow is as follows:

  • The mainline
    • The main thread starts by creating the global execution context and pushes it onto the call stack.
    • The main thread executes console.log function call, a function execution context for console.log is added to the call stack.
    • The main thread logs "Start" to the console.
    • The main thread removes function execution context for console.log from the call stack.
    • The main thread executes require function call, a function execution context for require is added to the call stack.
    • The main thread starts resolving the module identifier fs.
    • The main thread wraps the contents of fs in a function and executes it, a function execution context for fs is added to the call stack.
    • The module.exports object is returned and assigned to readable.
    • The main thread removes function execution context for fs from the call stack.
    • The main thread removes function execution context for require from the call stack.
    • The main thread executes fs.createReadStream function call, a function execution context for fs.createReadStream is added to the call stack.
    • The main thread initiates a non-blocking file system operation to read the current file.
    • This function is synchronous, meaning it runs immediately and returns a Readable stream object assigned to the readable.
    • The main thread moves the fs.createReadStream callback function to the I/O queue.
    • The main thread removes the function execution context for the first fs.createReadStream from the call stack.
    • The main thread executes readable.on function call, a function execution context for readable.on is added to the call stack.
    • The main thread moves the callback function to the event queue (task queue) which will be invoked whenever a chunk of data is available to be read from the stream.
    • This callback is an asynchronous event listener, meaning it will be called asynchronously whenever the 'data' event is emitted.
    • The main thread moves the readable.on callback function to the I/O queue.
    • The main thread executes the second readable.on function call, a function execution context for readable.on is added to the call stack.
    • The main thread moves the callback function to the event queue (task queue) which will be invoked when the stream is closed.
    • This callback is also asynchronous and will be called when the 'close' event is emitted.
    • The main thread moves the readable.on callback function to the I/O queue.
    • The main thread executes require function call, a function execution context for require is added to the call stack.
    • The main thread starts resolving the module identifier http.
    • The main thread wraps the contents of http in a function and executes it, a function execution context for http is added to the call stack.
    • The module.exports object is returned and assigned to http.
    • The main thread removes function execution context for http from the call stack.
    • The main thread removes function execution context for require from the call stack.
    • The main thread executes http.get function call, a function execution context for http.get is added to the call stack.
    • The main thread initiates initiates an HTTP GET request.
    • The main thread moves the http.get callback function to the I/O queue.
    • The main thread removes the function execution context for the first http.get from the call stack.
    • The main thread executes setTimeout function call, a function execution context for setTimeout is added to the call stack.
    • The main thread adds the setTimeout callback function to the timer queue.
    • The event loop continuously checks the timer queue. When the specified delay has elapsed, the callback function is moved to the appropriate phase of the event loop.
    • The main thread removes the function execution context for setTimeout from the call stack.
    • The main thread executes setImmediate function call, a function execution context for console.log is added to the call stack.
    • The main thread moves the setImmediate callback function to the check queue.
    • The main thread removes function execution context for setImmediate from the call stack.
    • The main thread executes process.nextTick function call, a function execution context for process.nextTick is added to the call stack.
    • The main thread moves the process.nextTick callback function to the next tick queue.
    • The main thread removes the function execution context for process.nextTick from the call stack.
    • The main thread executes queueMicrotask function call, a function execution context for queueMicrotask is added to the call stack.
    • The main thread moves the queueMicrotask callback function to the microtask queue.
    • The main thread removes the function execution context for queueMicrotask from the call stack.
    • The main thread executes console.log function call, a function execution context for console.log is added to the call stack.
    • The main thread logs "End" to the console.
    • The main thread removes function execution context for console.log from the call stack.
    • Once all the synchronous code has executed, the main thread removes the global execution context from the call stack.
    • The call stack is now empty, waiting for asynchronous callbacks to be executed.
  • The event loop starts processing the asynchronous callbacks that have been registered.
  • The first loop
    • The first loop starts by running the microtasks queue. The nextTick queue gets priority over the microtask queue.
      • The event loop pushes the task from the nextTick queue onto the call stack and creates a new function execution context for process.nextTick callback function.
      • The event loop executes console.log function call, a function execution context for console.log is added to the call stack.
      • The event loop logs "nextTick callback" to the console.
      • The event loop removes function execution context for console.log from the call stack.
      • The event loop removes the function execution context for process.nextTick callback function from the call stack.
      • The event loop pushes the task from the microtask queue onto the call stack and creates a new function execution context for queueMicrotask callback function.
      • The event loop executes console.log function call, a function execution context for console.log is added to the call stack.
      • The event loop logs "queueMicrotask callback" to the console.
      • The event loop removes function execution context for console.log from the call stack.
      • The callback function has no more code to run.
      • The event loop removes the function execution context for queueMicrotask callback function from the call stack.
    • Timers Phase
      • The event loop pushes the task from the timer queue onto the call stack and creates a new function execution context for setTimeout callback function.
      • The event loop executes console.log function call, a function execution context for console.log is added to the call stack.
      • The event loop logs "Timeout callback" to the console.
      • The event loop removes function execution context for console.log from the call stack.
      • The callback function has no more code to run.
      • The event loop removes the function execution context for setTimeout callback function from the call stack.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Pending Callbacks Phase
      • No pending callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Poll Phase
      • The event loop waits for data from streams.
      • The HTTP request is sent over the network and waits for the server response.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Check Phase
      • The event loop pushes the task from the check queue onto the call stack and creates a new function execution context for setImmediate callback function.
      • The event loop executes console.log function call, a function execution context for console.log is added to the call stack.
      • The event loop logs "Check callback" to the console.
      • The event loop removes function execution context for console.log from the call stack.
      • The callback function has no more code to run.
      • The event loop removes the function execution context for setImmediate callback function from the call stack.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Close Callbacks Phase
      • No close callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
  • The event loop will continue to loop.
  • The next loop
    • The first loop starts by running the microtasks queue.
      • No microtasks to execute.
    • Timers Phase
      • No timeout callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Pending Callbacks Phase
      • No pending callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Poll Phase
      • When a chunk of data is received, the event loop will move the callback function for data event listener from the event queue (task queue) to the call stack to be executed and create a new function execution context for callback function.
      • Each chunk of data triggers a data event on the readable stream object.
      • The event loop executes console.log function call, a function execution context for console.log is added to the call stack.
      • The event loop logs "Data event listener" to the console.
      • The event loop removes function execution context for console.log from the call stack.
      • The callback function has no more code to run.
      • The event loop removes function execution context for callback function from the call stack.
      • When the response headers from the server are received, the event loop pushes the task from the I/O queue onto the call stack and creates a new function execution context for http.get callback function.
      • The server localhost responds HTTP GET request, the data starts arriving in chunks.
      • The event loop executes the data event listener call, a function execution context for data event listener is added to the call stack.
      • The event loop moves the calbback function for data event listener to the event queue (task queue).
      • Each chunk of data triggers a data event on the response object.
      • Once the event loop detects the data event, the event loop will move the callback function for data event listener from the event queue (task queue) to the call stack to be executed and create a new function execution context for callback function.
      • The event loop executes console.log function call, a function execution context for console.log is added to the call stack.
      • The event loop logs "Data event listener callback" to the console.
      • The event loop removes function execution context for console.log from the call stack.
      • The callback function has no more code to run.
      • The event loop removes function execution context for callback function from the call stack.
      • The data event listener also has no more code to run.
      • The event loop removes function execution context for data event listener from the call stack.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Check Phase
      • No check callbacks to execute.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.
    • Close Callbacks Phase
      • When the entire file is read, the event loop will move the callback function for close event listener from the event queue (task queue) to the call stack to be executed and create a new function execution context for callback function.
      • The event loop executes console.log function call, a function execution context for console.log is added to the call stack.
      • The event loop logs "Close callback" to the console.
      • The event loop removes function execution context for console.log from the call stack.
      • The callback function has no more code to run.
      • The event loop removes function execution context for callback function from the call stack.
      • The event loop checks for any new callbacks added to the microtasks queue.
      • If no any new callbacks added to the microtasks queue, the event loop continues to next phase.