Node.js
threading
JavaScript
concurrency
programming tutorial

How to create threads in nodejs

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Node.js is single-threaded at the JavaScript event-loop level, but that does not mean it cannot use threads. If you want to run JavaScript code in parallel, the modern API is worker_threads; if you only need non-blocking I/O, you usually do not need to create threads manually at all.

First: Know the Difference Between Concurrency and Parallelism

Node already handles many I/O operations concurrently through the event loop and the underlying libuv thread pool. That is why code such as network requests or file reads often does not require you to create threads yourself.

You reach for worker threads when:

  • the work is CPU-heavy,
  • it would block the event loop,
  • and you want JavaScript to run in parallel on another thread.

For ordinary asynchronous I/O, async and evented APIs are still the normal answer.

Create a Worker Thread

The worker_threads module is the built-in way to do this.

javascript
1const { Worker, isMainThread, parentPort, workerData } = require('node:worker_threads');
2
3if (isMainThread) {
4  const worker = new Worker(__filename, {
5    workerData: 40,
6  });
7
8  worker.on('message', (result) => {
9    console.log('Result from worker:', result);
10  });
11
12  worker.on('error', (err) => {
13    console.error('Worker error:', err);
14  });
15
16  worker.on('exit', (code) => {
17    console.log('Worker exited with code', code);
18  });
19} else {
20  const answer = workerData + 2;
21  parentPort.postMessage(answer);
22}

When you run this file, the main thread starts a worker, the worker computes a result, and sends it back with postMessage.

Why Worker Threads Exist

JavaScript on the main thread shares one event loop. If you do expensive CPU work there, the whole process becomes unresponsive.

For example, a naive image transformation or large numeric loop can block:

  • HTTP request handling,
  • timers,
  • and other asynchronous callbacks.

Moving that work to a worker thread isolates it from the main event loop.

A Better CPU-Bound Example

Here is a simple worker that computes a sum:

javascript
1const { Worker, isMainThread, parentPort, workerData } = require('node:worker_threads');
2
3function heavySum(limit) {
4  let total = 0;
5  for (let i = 0; i < limit; i++) {
6    total += i;
7  }
8  return total;
9}
10
11if (isMainThread) {
12  const worker = new Worker(__filename, {
13    workerData: 50_000_000,
14  });
15
16  worker.on('message', (result) => {
17    console.log('Computed total:', result);
18  });
19} else {
20  parentPort.postMessage(heavySum(workerData));
21}

This kind of work is where worker threads actually help.

Message Passing and Shared Memory

By default, worker threads communicate with messages. That is the simplest and safest model:

  • main thread sends data,
  • worker does the work,
  • worker sends back a result.

Node also supports shared memory through SharedArrayBuffer, but that is a more advanced design. For most applications, plain message passing is the right starting point.

Worker Threads Are Not Free

Creating workers has overhead:

  • startup cost,
  • memory cost,
  • and serialization cost for messages.

That means you should not spawn a new worker for every tiny task. If you have lots of small CPU jobs, a worker pool is usually better than one worker per request.

Do Not Confuse Worker Threads With the libuv Thread Pool

Node already uses a background thread pool internally for some operations such as:

  • filesystem work,
  • DNS,
  • crypto,
  • and compression.

That internal pool is not the same as worker_threads. The libuv pool helps native and system tasks. Worker threads are for running JavaScript in parallel.

When to Use Processes Instead

Sometimes separate processes are better than threads:

  • stronger isolation,
  • separate memory spaces,
  • and easier crash containment.

Node's cluster module and process-based architectures are still useful, but they solve a different problem from lightweight in-process worker threads.

Common Pitfalls

The biggest pitfall is creating worker threads for I/O-bound work that was already non-blocking. That adds complexity without solving the real problem.

Another mistake is spawning too many workers or creating a new worker for every tiny task. Worker startup and messaging overhead can outweigh the benefit.

Developers also often forget that the main value of worker threads is protecting the event loop from CPU-bound work, not making every Node program automatically faster.

Finally, do not confuse worker threads with the built-in libuv thread pool. They are different mechanisms with different jobs.

Summary

  • Use worker_threads when you need CPU-bound JavaScript to run in parallel.
  • Do not create threads for ordinary async I/O unless there is a specific reason.
  • Workers communicate primarily through message passing.
  • Worker creation has overhead, so use pooling for many small jobs.
  • The event loop, the libuv thread pool, and worker threads are related but distinct parts of Node's concurrency story.

Course illustration
Course illustration

All Rights Reserved.