nodejs3 min

one thread, four helpers

is Node.js single-threaded? your JavaScript is. the file reads, DNS lookups and hashing underneath it aren't, and a stopwatch shows the difference.

is Node.js single-threaded? your JavaScript is. Node isn't.

#the event loop

your code runs on one thread, in a loop. the event loop goes around in phases: run the timers that are due, wait for I/O and run its callbacks, run the setImmediate callbacks, run the close callbacks, and around again (there are two more phases, but they're internal). one callback at a time, never two at once.

so if one callback takes long, everything else waits. this asks for a 10ms timer, then adds up a billion numbers:

const start = performance.now();
setTimeout(() => {
  console.log(`asked for 10ms, ran at ${Math.round(performance.now() - start)}ms`);
}, 10);
 
let n = 0;
for (let i = 0; i < 1_000_000_000; i++) n += i;

on my machine (Node 24, a Ryzen 7 3700X) it printed asked for 10ms, ran at 747ms, twice in a row. the timer was ready at 10ms, but the loop was busy adding.

#where the other threads are

reading a file or hashing a password doesn't happen on that thread. Node hands it to libuv, the C library underneath it, which keeps a pool of threads for the work the operating system can't do without blocking. per Node's own guide, that's the file system, dns.lookup, the expensive parts of crypto and zlib. network sockets never go there: the operating system tells libuv when they're ready.

the pool has four threads, unless you set UV_THREADPOOL_SIZE (up to 1024). you can watch the four of them work:

import { pbkdf2 } from 'node:crypto';
 
const start = performance.now();
for (let i = 1; i <= 8; i++) {
  pbkdf2('password', 'salt', 1_000_000, 64, 'sha512', () => {
    console.log(`hash ${i} done at ${Math.round(performance.now() - start)}ms`);
  });
}

eight hashes, four threads: the first four finished between 463 and 473ms, the next four between 929 and 947ms. with UV_THREADPOOL_SIZE=8, all eight were done by 675ms, since my CPU has 16 threads to put them on.

#threads of your own

if your own JavaScript is the slow part, worker_threads runs it on another thread, with its own event loop, talking to the main one through messages. that's as close as Node gets to multi-threaded JavaScript, and you have to ask for it.

so: one thread for your code, four helpers underneath, more if you ask.