Node.js Internals: How Node.js Works Behind The Scene

Introduction
Node.js is a JavaScript runtime environment. It is built on V8 engine of Google Chrome, plus C++ bindings. As we know that JavaScript is a single threaded language for that same reason Node.js uses libuv library to run I/O callbacks which requires heavy CPU memory. Libuv manages thread pool which handle asynchronous I/O operations and by default it is 4 in numbers. These all things help Node.js to beautifully run thousands of request easily at a time.
In this blog we’ll learn how Node.js works behind the scenes and how it handles thousands of request as a runtime environment of a single threaded language JavaScript. We’ll also learn what is event loop, why it is termed as the heart of the Node.js, and the sequence in which code is executed.
Node.js Internals
When Node.js came to market, it freed JavaScript from the browser, allowing developers to make frontend and backend without needing to know any other language other than JavaScript. Though there are only subtle differences between Node.js and JavaScript, their internals are totally different from each other. For example setImmediate() is part of Node.js but not the browser.
Let us take an example to understand how Node works internally.
console.log('Start');
setImmediate(() => console.log('setImmediate'));
setTimeout(() => console.log('setTimeout'),0);
console.log('End');
Can you guess the outcome? …well you could be right or wrong but we are not here to be right - wrong we are here to learn ‘why’ and ‘how’ (will see output at the end). Now let’s see how code gets executed in Node.js.
The moment script runs all of the top level code gets executed synchronously. Then event callbacks register and after that comes the role of event loop the heart of the Node.js it has many phases which we will discuss in detail.
Event loop and its phases
Without event loop Node.js wouldn’t have been as beautiful and popular as it is now. Event loop allows Node.js to perform non-blocking asynchronous code despite being single threaded.
The phases of event loop:-
Timer Phase: The first phase of the event loop is the timer phase. It executes setTimeout() and setInterval() as these are the only two timers in JavaScript.
setTimeout(() => console.log('Timer Phase: setTimeout'));
console.log('Hello');
//output:-
//Hello
//Timer Phase: setTimeout
Pending Callbacks: The second phase is pending callbacks. This phase executes certain system-level callbacks, generally not used directly in user code.
These are typically related to operations that were deferred from a previous event loop iteration, often due to system-level conditions or errors.
const net = require('net');
const client = net.createConnection({ port: 8080, host: 'localhost'}, () => {
console.log('Connected to server!') } );
client.on('error', (err) => console.log('Pending Callback Phase: client error',err));
Poll Phase: In third phase I/O callbacks are executed such as reading or writing files.
const fs = require('fs');
console.log('Start');
fs.readFile(__filename,() => {
console.log('Poll Phase: fs.readFile callback');
});
console.log('End');
Check Phase: setImmediate() is executed in this phase after current I/O operations have completed.
console.log('Start');
setImmediate(()=> console.log('Check Phase: setImmediate callback'));
console.log('End');
Close Callbacks Phase: In this phase, callbacks are run that needs to be executed after server exit or close.
const net = require('net');
const socket = net.connect(80, 'example.com');
socket.on('connect', () => {
socket.end();
})
Last but not least MicroTask queue is executed after the current operation and before the event loop proceeds to next phase. This includes process.nextTick and Promises (.then, .catch, .finally).
Example:-
console.log('start');
process.nextTick(()=> console.log('Microtask: nextTick callback'))
Promise.resolve().then(()=> console.log('Microtask: Promise.then callback'))
console.log('End');
Now that we have covered Node internals and phases of event loop let’s again see the first code mentioned before:-
console.log('Start');
setImmediate(() => console.log('setImmediate'));
setTimeout(() => console.log('setTimeout'),0);
console.log('End');
If we see the process we just read it will be concluded that the output will be:-
// Start
// End
// setTimeout
// setImmediate
But we have to be careful here as this output is not decided solely by the event loop but also by how the code is observed. That can cause the last two outputs’ order to vary that is setTimeout and setImmediate.
In conclusion the event loop order is fixed but timings when callbacks are added to queue (top-level vs. I/O callbacks ) decides the observed order resulting in an order-may-vary situation..
So the actual output will be :-
// Start
// End
// setTimeout (or setImmediate) // order may vary
// setImmediate (or setTimeout) // order may vary
In summary, the Node.js event loop handles asynchronous operations by splitting work into phases: Timers → Pending Callbacks → Poll → Check → Close Callbacks, plus Microtasks in between.
Understanding these phases helps us write efficient, non-blocking code.
Node.js may be single-threaded, but thanks to the event loop and libuv, it can handle thousands of concurrent operations beautifully.




