V8 and libuv

V8, libuv and the Shape of the Runtime

The node binary is a C++ program embedding several independent libraries, and knowing which one owns what turns most performance questions into easy ones. V8 86,723 runs the JavaScript: it owns the heap, the garbage collector and the ECMAScript feature set, so your language level follows the V8 build your Node carries. libuv owns everything else — the event loop, the abstraction over epoll, kqueue and Windows IOCP, timers, signals, child processes, and a small pool of worker threads. Around them sit OpenSSL, zlib, brotli, zstd, undici 7,705 for fetch, c-ares for dns.resolve() and Ada for URL parsing. node -p "process.versions" prints each version.

The layers inside a single Node.js process
The layers inside a single Node.js 2,131 process

Most asynchronous work never touches a thread: sockets, pipes and dns.resolve() go straight to the kernel's notification mechanism. What cannot runs on a threadpool of four threads by default — everything in node:fs except the watchers, crypto.pbkdf2(), scrypt(), randomBytes(), generateKeyPair(), dns.lookup() and zlib. Four is easy to saturate.

Saturating the libuv threadpoolJavaScript
import { pbkdf2 } from 'node:crypto';
const t0 = performance.now();
for (let i = 0; i < 8; i++) {
  pbkdf2('password', 'salt', 400000, 64, 'sha512', () => {
    console.log(`job ${i} finished at ${(performance.now() - t0).toFixed(0)} ms`);
  });
}
Output
job 1 finished at 228 ms
job 0 finished at 229 ms
job 2 finished at 230 ms
job 3 finished at 234 ms
job 4 finished at 488 ms
job 5 finished at 494 ms
job 7 finished at 498 ms
job 6 finished at 513 ms

Eight jobs, two clean waves of four. Re-run with UV_THREADPOOL_SIZE=8 and all eight finish together near 230 ms. Set that variable before the process starts: the pool is built during runtime initialization, so assigning process.env.UV_THREADPOOL_SIZE in code does nothing. Raise it past your core count only for genuinely I/O-bound work — pbkdf2 burns CPU, and extra threads there buy only context switching.