Skip to content

Node.js Runtime

"Runtime" is a word you will see constantly in Node.js discussions. This lesson unpacks what the Node.js runtime is actually made of, V8, libuv, and native bindings, and how they work together to execute your JavaScript.

What Makes Up the Node.js Runtime

The Node.js runtime is not a single piece of software, it is a combination of Google's V8 JavaScript engine (which parses and executes your JS), libuv (a C library providing the event loop, thread pool, and cross-platform async I/O), and a layer of C++ bindings that expose OS-level features like the file system and networking to JavaScript.

When you call an async function like fs.readFile(), your JavaScript call goes through a C++ binding into libuv, which either performs the work on a background thread (for file I/O) or delegates to the OS (for network I/O), then queues a callback to run back on the main JavaScript thread once the work finishes.

const fs = require('node:fs');

console.log('1: starting read');

fs.readFile(__filename, 'utf8', (err, data) => {
  console.log('3: file read complete, length =', data.length);
});

console.log('2: read scheduled, moving on');

Notice the output order: 1, 2, 3. The file read happens off the main thread via libuv, so "2" logs before the callback in "3" ever runs.

The Runtime Layers

Your JavaScript code
        |
        v
   Node.js APIs (fs, http, crypto, ...)
        |
        v
   C++ bindings
        |
        v
   libuv (event loop + thread pool)  <-- V8 (executes JS)
        |
        v
   Operating System
  • V8 compiles and executes your JavaScript; it has no knowledge of files or sockets on its own.
  • libuv supplies the event loop and a thread pool for operations the OS can't do asynchronously itself.
  • C++ bindings are the glue that lets JavaScript call into libuv and the OS.
  • Node.js APIs like fs, http, and crypto are JavaScript wrappers around these lower layers.

Node.js Runtime Cheatsheet

The core components of the runtime and what each one is responsible for.

Component Responsibility
V8 Parses and executes JavaScript, manages memory/garbage collection
libuv Event loop, thread pool, async file and network I/O
C++ bindings Bridge between JavaScript and native OS features
Node.js APIs fs, http, crypto, etc, the JS-facing standard library
Event loop Decides which callback runs next, phase by phase
Thread pool Handles blocking work like file system calls, default size 4

V8's Role: Just the JavaScript

V8 is a general-purpose JavaScript engine, it also powers Chrome and Chromium-based browsers. On its own, V8 knows nothing about files, sockets, or timers; it only understands the ECMAScript language and provides a JavaScript execution environment plus memory management and garbage collection.

Everything that feels distinctly "Node.js" (reading files, starting servers, spawning processes) is added on top of V8 by Node's own C++ layer and libuv, not by V8 itself.

libuv and the Thread Pool

libuv gives Node.js its event loop and a small internal thread pool (four threads by default). Network I/O typically doesn't need this thread pool since operating systems already provide async socket APIs, but file system operations, DNS lookups, and some crypto functions do use it because their underlying OS calls are blocking.

  • Default thread pool size is 4, configurable via the UV_THREADPOOL_SIZE environment variable.
  • CPU-heavy synchronous code still blocks the single JavaScript thread, libuv's thread pool doesn't help there.
  • This is why CPU-bound work belongs in Worker Threads, not regular async callbacks.

Common Mistakes

  • Assuming Node.js is "purely single-threaded", the JavaScript thread is single, but libuv's thread pool and background OS threads are not.
  • Believing async file operations run instantly in parallel with unlimited concurrency, they share a small default thread pool.
  • Confusing V8 (executes JS) with Node.js (the full runtime built around V8).
  • Running CPU-intensive synchronous work and expecting the event loop to stay responsive.

Key Takeaways

  • The Node.js runtime combines V8 (JavaScript execution) with libuv (event loop and async I/O).
  • C++ bindings connect JavaScript APIs like fs and http to native OS functionality.
  • A small thread pool (default size 4) backs blocking operations like file system calls.
  • CPU-bound synchronous code still blocks the single JavaScript thread regardless of libuv.

Pro Tip

If you ever need to increase concurrency for heavy file I/O, try raising UV_THREADPOOL_SIZE before assuming you need a completely different architecture, it's a one-line environment variable change.