The Event Loop Is Not Multithreading: The Most Common Concurrency Misconception in Node.js

A persistent misconception in the Node.js world goes something like this: “Node can handle tens of thousands of concurrent connections, so it must be doing multithreading.” It isn’t. Node’s concurrency is built on a single-threaded event loop combined with non-blocking I/O. There is exactly one main thread. The impression of simultaneity comes from the fact that I/O operations — file reads, network calls, database queries — do not hold the CPU while they wait. The loop moves on to other events and comes back when the I/O resolves. That is not parallelism; it is cooperative scheduling.

The mental model I find most useful: picture a single very quick waiter working a busy restaurant. He takes orders at every table, steps away while the kitchen cooks, and comes back when something is ready. That works brilliantly right up until one table orders a dish that takes 30 minutes to prepare — and the waiter decides to stand there and watch it cook. Every other table waits. I/O-heavy workloads (API gateways, WebSocket fan-out, proxies) rarely make the waiter stand still, so the loop stays fast. CPU-heavy workloads (image processing, cryptographic work, real-time game physics) are that 30-minute dish. Nothing else gets served while the main thread is occupied.

worker_threads exists precisely for this case, and it does work — but it is not free. You pay in serialisation overhead, message-passing latency, and the extra design surface that cross-thread communication demands. This is part of why I reach for a different language or runtime on certain backends. Not because JavaScript is flawed, but because the tool does not fit the job. A game server doing real-time physics simulation patched together with worker_threads is a harder path than simply starting with something designed for that load. An I/O-dominated API service that needs fast iteration? Node is a sensible default there.

Tool selection is ultimately about acknowledging that every tool has a boundary, then matching it to the workload rather than forcing the workload to conform to the tool.

Key Takeaways

  • Node.js handles high concurrency through non-blocking I/O and the event loop, not multithreading — there is always exactly one main thread.
  • I/O-heavy workloads are the sweet spot; CPU-heavy work blocks the entire event loop and degrades every concurrent connection.
  • worker_threads can offload CPU work but introduces real costs in serialisation and inter-thread coordination.
  • “It runs” is not the same as “it fits” — understand your workload’s character before choosing a runtime.
  • The event loop in one image: one waiter, many tables, one dish that takes forever, everyone waits.

Sheng’s take, drafted with Claude · part of the 2026-06-13 blog renovation, paint still drying.