This post explains a quiz originally shared as a LinkedIn poll.
🔹 The Question
const worker = new Worker(URL.createObjectURL(
new Blob([`
onmessage = ({ data }) => {
data.count++;
postMessage(data);
};
`], { type: 'application/javascript' })
));
const obj = { count: 0 };
worker.onmessage = ({ data }) => {
console.log('Worker:', data.count);
console.log('Original:', obj.count);
};
worker.postMessage(obj);
obj.count = 5;
Hint: postMessage doesn't hand off your object — it makes a copy at the exact moment you call it.
🔹 Solution
Output:
Worker: 1
Original: 5
Correct Answer: C) Worker: 1, Original: 5
🧠 How this works
The core mechanism at play is the structured clone algorithm — the way browsers serialize data when transferring it between the main thread and a Web Worker via postMessage.
When you call worker.postMessage(obj), the browser does not pass a reference to obj. It performs a deep clone of the object at the exact moment postMessage is invoked. The worker receives a completely independent copy of { count: 0 }.
JavaScript's Web Workers run in a separate execution context with no shared object memory. This is by design — it prevents the race conditions and data corruption that plague shared-memory concurrency models. The only way to share raw memory between threads is via SharedArrayBuffer, and even that only works for typed arrays, not arbitrary objects.
This behavior catches developers who treat postMessage like a function call with pass-by-reference semantics. In production, this leads to state desynchronization between the main thread and the worker — a class of bug that's difficult to reproduce because it depends on timing.
🔍 Line-by-line explanation
const worker = new Worker(URL.createObjectURL(...))— Creates an inline Web Worker from a Blob URL. The worker code listens for incoming messages, incrementsdata.countby 1, and sends the modified object back to the main thread.const obj = { count: 0 }— Creates an object on the main thread withcountinitialized to0.worker.onmessage = ({ data }) => { ... }— Registers a handler to process messages coming back from the worker. This callback fires asynchronously, only after the worker has processed its task.worker.postMessage(obj)— This is the critical line. At this exact moment,objis{ count: 0 }. The structured clone algorithm creates a deep copy of{ count: 0 }and queues it for delivery to the worker. The originalobjon the main thread is completely unaffected.obj.count = 5— Executes synchronously on the main thread, immediately afterpostMessage. Setsobj.countto5. This mutation has zero effect on the copy already sent to the worker — the clone was a snapshot taken at step 4.Inside the worker: The worker receives
{ count: 0 }(the cloned snapshot), incrementscountto1, and callspostMessage({ count: 1 })to send the result back. This response is itself structured-cloned for the return trip.Back on the main thread: The
onmessagehandler fires asynchronously.data.countis1(from the worker's cloned response).obj.countis5(from the main thread's synchronous mutation in step 5).
The non-obvious part: postMessage looks like a synchronous function call, but the cloning happens synchronously while delivery is asynchronous. Developers often assume mutations made after postMessage will "make it in time" — they won't, because the snapshot was already taken.
🔹 Key Takeaways
-
postMessageuses the structured clone algorithm — objects are deep-copied at call time, not passed by reference. Think of it as taking a snapshot. -
Mutations after
postMessagenever reach the worker — the clone is frozen at the moment of the call. Any subsequent changes to the original object are invisible to the worker. -
Web Workers have no shared object memory — the only shared-memory primitive is
SharedArrayBuffer, which works exclusively with typed arrays, not regular JavaScript objects. - Treat worker communication like a network boundary — send immutable messages, receive responses, and reconcile state explicitly. Never assume the worker and main thread see the same data.
-
Use
Transferableobjects for performance — for largeArrayBufferpayloads, transferring ownership avoids the copy cost entirely, but the sender loses access to the transferred buffer.
Top comments (0)