Back to 20 Concepts
internals • Advanced
Libuv Thread Pool & UV_THREADPOOL_SIZE
Node.js is single-threaded for JS execution, but Libuv maintains a background C thread pool (default 4 threads) to handle synchronous OS tasks: file system (fs), cryptography (crypto), compression (zlib), and DNS lookups.
Intuitive Mental Model
The 4 Secret Agents: When the manager (Main Thread) receives heavy crypto hashing or disk file reading, it hands the mission to one of 4 background agents (Thread Pool), who report back when finished.
Node.js ESM / CJS ImplementationNode.js v22 LTS
// Set thread pool size (Must be set BEFORE any async calls!):
process.env.UV_THREADPOOL_SIZE = '8';
import crypto from 'crypto';
const start = Date.now();
// 4 crypto hashes run concurrently on 4 default threads (~100ms):
for (let i = 0; i < 4; i++) {
crypto.pbkdf2('pass', 'salt', 100000, 64, 'sha512', () => {
console.log(`Hash ${i + 1} finished in ${Date.now() - start}ms`);
});
}Key Architectural Takeaways
- •Default thread pool size is 4; can be increased up to 1024 via UV_THREADPOOL_SIZE environment variable.
- •Only 4 specific subsystems use the Thread Pool: fs, crypto (pbkdf2, randomBytes), zlib, and dns.lookup.
- •Network I/O (HTTP, TCP, UDP, TLS) does NOT use the thread pool; it uses OS non-blocking epoll/kqueue sockets directly.
Common Production Mistake
Setting process.env.UV_THREADPOOL_SIZE inside application code after importing fs/crypto, which is ignored by Libuv.
Recommended Solution
Set UV_THREADPOOL_SIZE=8 in the shell command or Dockerfile before the Node process boots.