"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.
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.
You now understand the layers underneath every Node.js program. Next, compare Node.js with the browser environment in Node.js vs Browser.