On this page

Thread Pool

libuv hands some async work to the operating system, but some work the OS can't do asynchronously. For that, libuv keeps a small team of background threads ready. This is the Thread Pool.

Definition

The thread pool is a fixed group of background threads managed by libuv. It runs slow or blocking operations (such as file reads and password hashing) off the main thread, so your JavaScript keeps running.

Analogy: think of a bank with one front desk and 4 back-office clerks. The front desk (main thread) takes requests and passes slow paperwork to the clerks (thread pool). If 6 requests arrive, 4 clerks start at once, and the other 2 wait until a clerk is free.

PropertyThread Pool
Managed bylibuv
Default size4 threads
Maximum size1024
Changed withThe UV_THREADPOOL_SIZE environment variable
RunsC/C++ work for Node.js APIs, not your JavaScript

What Uses the Thread Pool

Uses the thread poolDoes NOT use the thread pool
File system: fs.readFile, fs.writeFile, and other async fs methodsNetwork I/O: http, net, TCP, sockets (handled by the OS)
Crypto: crypto.pbkdf2, crypto.scrypt, crypto.randomBytes (async)Timers: setTimeout, setInterval (handled by the event loop)
Compression: zlib async methodsdns.resolve() (uses the network directly)
DNS: dns.lookup() (also used by http.get for hostnames)Sync methods like fs.readFileSync (run on the main thread)

Seeing the Limit of 4 Threads

crypto.pbkdf2 is a slow hashing function that runs on the thread pool. Let's call it 6 times:

const crypto = require("crypto");
const start = Date.now();
for (let i = 1; i <= 6; i++) {
crypto.pbkdf2("password", "salt", 100000, 64, "sha512", () => {
console.log(`Hash ${i}: ${Date.now() - start} ms`);
});
}
// Example output (times vary by machine):
// Hash 1: 510 ms
// Hash 3: 515 ms
// Hash 2: 518 ms
// Hash 4: 520 ms
// Hash 5: 1020 ms
// Hash 6: 1025 ms
  1. All 6 tasks are handed to libuv at once.
  2. 4 threads take the first 4 tasks, and they finish at about the same time.
  3. Tasks 5 and 6 wait in line until a thread is free, so they take about twice as long.

The order of the first 4 can change on each run, because they finish in parallel.

Changing the Thread Pool Size

Set UV_THREADPOOL_SIZE when you start the program:

# Linux / macOS
UV_THREADPOOL_SIZE=6 node app.js
# Windows (PowerShell)
$env:UV_THREADPOOL_SIZE=6; node app.js

With 6 threads, all 6 hashes from the example finish at about the same time, as long as your machine has at least 6 CPU cores.

When to increase it:

  • Your app does a lot of file system, crypto, or compression work at the same time.
  • Many slow tasks are waiting in line for a free thread.

Be careful: more threads than CPU cores does not make CPU-heavy work faster. The threads just take turns on the same cores.

Thread Pool Starvation

All thread pool tasks share the same 4 threads. If slow tasks fill all of them, other tasks must wait, even unrelated ones.

const crypto = require("crypto");
const fs = require("fs");
// 4 slow hashes fill all 4 threads
for (let i = 0; i < 4; i++) {
crypto.pbkdf2("password", "salt", 500000, 64, "sha512", () => {});
}
// This small file read must wait for a free thread
fs.readFile("small.txt", () => {
console.log("File read (delayed)");
});

The file is tiny, but it is delayed because all threads are busy hashing. This can also slow down http.get calls, because hostname lookups use dns.lookup.

Thread Pool vs Worker Threads

Thread PoolWorker Threads
Created bylibuv, automaticallyYou, with the worker_threads module
RunsNode's built-in C/C++ work (fs, crypto, zlib)Your own JavaScript
Use it forNothing to do; Node uses it for youCPU-heavy JavaScript, like image processing or big calculations
ControlOnly its sizeFull control

So if your own JavaScript is heavy, the thread pool won't help. Use worker threads instead.

Summary

  • The thread pool is a set of background threads managed by libuv.
  • The default size is 4, changed with UV_THREADPOOL_SIZE (maximum 1024).
  • It runs async fs, crypto, zlib, and dns.lookup.
  • Network I/O and timers do not use it.
  • With 4 threads, the 5th task waits until a thread is free.
  • Slow tasks can starve the pool and delay unrelated tasks.
  • The thread pool runs Node's built-in work. For your own CPU-heavy JavaScript, use worker threads.

Interview Answer: What Is the Thread Pool in Node.js?

The thread pool is a group of background threads managed by libuv. Node.js uses it for operations the operating system can't do asynchronously, such as async file system calls, crypto functions like pbkdf2, zlib compression, and dns.lookup. Network I/O does not use it, because the OS handles it asynchronously.

By default, the pool has 4 threads, and I can change it with the UV_THREADPOOL_SIZE environment variable. If more than 4 tasks are started at once, the extra tasks wait for a free thread. For example, if I run crypto.pbkdf2 6 times, the first 4 finish together, and the last 2 take about twice as long.

The thread pool only runs Node's built-in C/C++ work, not my JavaScript. For CPU-heavy JavaScript, I use worker threads.

The next topic is worker threads.

Next TopicBlocking vs Non-Blocking I/O