How do you pass data between threads?
1. How thread interaction is set up
Every thread (Worker) in Node.js:
- has its own context (doesn't share variables with others),
- runs within a single process,
- can exchange messages with the main thread through message passing,
- can, when needed, share memory via
SharedArrayBuffer.
In other words:
Threads can't see each other's global variables, but they can talk through specially provided channels.
2. The basic way: message passing
This is the most common and safest way to exchange data between threads.
Example:
main.js
const { Worker } = require('worker_threads');
const worker = new Worker('./worker.js');
// Send data to the thread
worker.postMessage({ name: 'Tim', numbers: [1, 2, 3] });
// Get the reply
worker.on('message', (result) => {
console.log('Reply from the thread:', result);
});worker.js
const { parentPort } = require('worker_threads');
// Receive a message from the main thread
parentPort.on('message', (data) => {
const sum = data.numbers.reduce((a, b) => a + b, 0);
parentPort.postMessage({ greeting: `Hello, ${data.name}!`, sum });
});How it works:
main.jscreates a thread and sends data viapostMessage().- The thread receives it through
parentPort.on('message', ...). - After computing, it sends the reply back via
parentPort.postMessage().
3. Passing data at startup: workerData
You can pass data once, when the thread is created, using the workerData option.
Example:
main.js
const { Worker } = require('worker_threads');
const worker = new Worker('./worker.js', {
workerData: { n: 5 }
});
worker.on('message', result => console.log('Result:', result));worker.js
const { workerData, parentPort } = require('worker_threads');
let fact = 1;
for (let i = 1; i <= workerData.n; i++) fact *= i;
parentPort.postMessage({ factorial: fact });Here, the data (n: 5) is passed right when the worker is created, with no postMessage() needed.
4. Sharing memory: SharedArrayBuffer
This is an advanced technique that lets threads share a region of memory, avoiding copying data and speeding up exchange.
Use it when:
- you need to exchange large arrays often,
- top performance matters,
- low-level operations are acceptable.
Example:
main.js
const { Worker } = require('worker_threads');
const shared = new SharedArrayBuffer(4 * Int32Array.BYTES_PER_ELEMENT);
const sharedArray = new Int32Array(shared);
sharedArray[0] = 42;
const worker = new Worker('./worker.js', { workerData: shared });
worker.on('exit', () => {
console.log('Changed by the worker:', sharedArray[0]); // 84
});worker.js
const { workerData } = require('worker_threads');
const arr = new Int32Array(workerData);
arr[0] *= 2; // Modify the shared valueHere:
SharedArrayBuffercreates a shared memory region available to both threads.- Changes made in one thread are visible in the other immediately.
Atomicscan be used for safe operations (to avoid data races).
5. Passing large data without copying: Transferable objects
Node.js supports passing some objects by reference (instead of copying),
for example ArrayBuffer, MessagePort, SharedArrayBuffer.
An example of passing a large buffer without copying:
const { Worker } = require('worker_threads');
const buffer = new ArrayBuffer(8);
const worker = new Worker('./worker.js');
worker.postMessage(buffer, [buffer]); // passed by reference (buffer is now unavailable in main)
worker.on('message', () => console.log('The worker got the buffer!'));worker.js
const { parentPort } = require('worker_threads');
parentPort.on('message', (buffer) => {
console.log('Buffer received in the worker, size:', buffer.byteLength);
parentPort.postMessage('OK');
});After the transfer, the object becomes "detached" (no longer accessible on the sender's side).
6. Summary of ways to pass data
| Way | Direction | Notes |
|---|---|---|
postMessage() / on('message') | Two-way | The most common way, safe |
workerData | Into the worker at creation | A one-time transfer at startup |
SharedArrayBuffer | Shared memory | Very fast, but needs care |
Transferable objects | A one-time transfer by reference | No copying, the source loses access after the transfer |
7. Choosing an approach
| Scenario | What to use |
|---|---|
| Ordinary data (objects, JSON, numbers) | postMessage() |
| Passing parameters at startup | workerData |
| Exchanging large amounts of data | SharedArrayBuffer |
| Maximum performance, no copying | Transferable objects |
| Several workers, independent tasks | postMessage() + message queues |
Summary
| Characteristic | Value |
|---|---|
| Threads are isolated from each other | Yes |
| Are shared variables available? | No |
| How they communicate | Via message passing or shared memory |
| Passing data at startup | Via workerData |
| Shared memory | SharedArrayBuffer + Atomics |
| The safe option | postMessage() |
Conclusion:
In Node.js, threads don't share state directly.
To exchange data, use:
- messages (postMessage / parentPort), a safe, simple way;
- shared memory (SharedArrayBuffer), fast, but requires care;
- transferable objects, for efficiently passing large structures.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.