Mayur Shelke
Back to blogs
How Node.js Works Internally — The Event Loop Explained
Node.jsJavaScriptBackend

How Node.js Works Internally — The Event Loop Explained

Understanding V8, libuv, and the event loop architecture

Mayur ShelkeFeb 28, 202610 min read

Node.js revolutionized server-side development by bringing JavaScript to the backend. But how does it actually work under the hood? Understanding Node.js internals will make you a significantly better backend developer.

The Big Picture

Node.js is built on top of two main components: V8 (Google's JavaScript engine that compiles JS to machine code) and libuv (a C library that provides async I/O, the event loop, and the thread pool). Together, they enable Node.js to handle thousands of concurrent connections with a single thread.

V8 Engine — JavaScript Execution

V8 compiles JavaScript directly to native machine code using Just-In-Time (JIT) compilation. It uses two compilers: Ignition (a bytecode interpreter for quick startup) and TurboFan (an optimizing compiler for hot functions). V8 also handles memory management through garbage collection using a generational approach.

libuv — The Async I/O Engine

libuv is the secret sauce behind Node.js's non-blocking I/O. It provides an event loop, a thread pool (default 4 threads), async TCP/UDP sockets, file system operations, child processes, and more. When Node.js needs to perform I/O, libuv delegates it to the OS kernel or its thread pool.

The Event Loop — Phase by Phase

The event loop is the core of Node.js. It runs in phases, each with its own queue of callbacks. The phases execute in this order: Timers (setTimeout, setInterval), Pending Callbacks (I/O callbacks deferred to the next iteration), Idle/Prepare (internal use only), Poll (retrieve new I/O events and execute callbacks), Check (setImmediate callbacks), and Close Callbacks (cleanup like socket.on('close')).

Microtasks vs Macrotasks

Between each phase of the event loop, Node.js processes the microtask queues. process.nextTick() callbacks run first, followed by resolved Promise callbacks. This is why a Promise.resolve().then() always runs before a setTimeout(..., 0). Understanding this priority is crucial for writing predictable async code.

The Thread Pool

Despite being single-threaded for JavaScript execution, Node.js uses a thread pool (managed by libuv) for operations that can't be handled asynchronously by the OS. This includes file system operations (fs.readFile, fs.writeFile), DNS lookups (dns.lookup), some crypto operations, and zlib compression. The default pool size is 4 threads, configurable via UV_THREADPOOL_SIZE.

Common Misconceptions

Node.js is NOT single-threaded — JavaScript execution is single-threaded, but Node.js uses multiple threads for I/O. The event loop does NOT run in a separate thread — it runs in the main thread. async/await does NOT make code run in parallel — it's syntactic sugar over Promises, which are still single-threaded.

Performance Tips

Avoid blocking the event loop with CPU-intensive tasks. Use Worker Threads for CPU-bound operations. Keep callbacks and Promise handlers lightweight. Use streaming for large data processing. Monitor the event loop lag to detect performance issues.

Conclusion

The event loop is what makes Node.js incredibly efficient for I/O-heavy applications. By understanding how V8, libuv, and the event loop work together, you can write more performant code and debug issues that would otherwise be mysterious.

Back to all blogs