postMessage() between threads
postMessage() is the method that sends a message from one context to another, for example from the main thread into a Web Worker or back. It does not touch the other side's memory directly; it hands over a copy of the data asynchronously through the message passing mechanism.
Theory
TL;DR
postMessage()puts a message into another context's queue and returns immediately; it returns nothing.- The data is serialised by the Structured Clone algorithm, so the other side gets a copy, not a reference.
- The receiver reads the data in a
messageevent handler throughevent.data. - Transferable objects (
ArrayBufferand friends) move ownership without copying, which is 10 to 100 times faster on large payloads. - The channel is two way: both sides can send and receive.
- The method is universal: Web Worker, iframe, other tabs, Service Worker.
Quick example
// main.js
const worker = new Worker('worker.js');
// send a message to the worker
worker.postMessage({ task: 'sum', a: 5, b: 7 });
// receive the reply
worker.onmessage = (event) => {
console.log('Worker reply:', event.data);
};// worker.js
onmessage = (event) => {
const { task, a, b } = event.data;
if (task === 'sum') {
postMessage(a + b); // send the result back
}
};How it works with a Web Worker
Step by step:
- The main thread calls
worker.postMessage(...). - The message is serialised and placed into the worker's message queue.
- The
onmessageevent fires inside the worker. - The worker does its processing and replies through
postMessage(). - The main thread catches that event through
onmessage.
All of this is asynchronous: JavaScript does not wait for the worker to answer.
The channel works in both directions:
| From | Method | Listener |
|---|---|---|
| Main thread to worker | worker.postMessage() | onmessage inside the worker |
| Worker to main thread | postMessage() | worker.onmessage |
An echo worker example
// worker.js
onmessage = (event) => {
postMessage(`Echo: ${event.data}`);
};// main.js
worker.postMessage('Hello!');
worker.onmessage = (e) => console.log(e.data);
// -> "Echo: Hello!"What exactly can be sent
You can send:
- primitives (
string,number,boolean,null,undefined); - objects (JSON compatible);
ArrayBuffer,Blob,File,ImageBitmap;- Transferable objects, which are handed over without copying.
Transferable Objects
Normally data is copied when it crosses threads (through structured clone). For large binary payloads that is expensive.
Transferable Objects (for example
ArrayBuffer) can be "passed by reference", without copying, simply by moving ownership.
const buffer = new ArrayBuffer(1024);
worker.postMessage(buffer, [buffer]); // hand it over as transferableAfter the transfer, buffer becomes unusable in the main thread, because ownership moved to the worker. On large payloads this is 10 to 100 times faster.
What postMessage() returns
Nothing. It simply queues the message for the other side, and the receiver catches it through the message event:
worker.onmessage = (event) => {
console.log(event.data);
};postMessage is not only for Web Workers
The method is universal and works across different browser contexts:
| Where it is used | Example |
|---|---|
| window to iframe | iframe.contentWindow.postMessage(data, origin) |
| iframe to parent | window.parent.postMessage(data, origin) |
| between tabs | otherWindow.postMessage(data, origin) |
| Service Worker and main thread | navigator.serviceWorker.controller.postMessage(data) |
| Web Worker and main thread | worker.postMessage(data) |
A window.postMessage() example between pages
// Page A
const popup = window.open('https://example.com/popup.html');
popup.postMessage('Hello, page B!', 'https://example.com');
// Page B
window.addEventListener('message', (event) => {
if (event.origin === 'https://example.com') {
console.log('Message received:', event.data);
}
});This is very handy for safe interaction between different domains, without breaking the same origin policy.
How it works under the hood
- When
postMessage()is called, the data is serialised with the Structured Clone Algorithm. - A copy of the data is placed into the receiver's queue.
- When that thread is free, the browser calls the
onmessage(event)handler. - The data is available in
event.data.
What is inside event
onmessage = (event) => {
console.log(event.data); // the data itself
console.log(event.origin); // the source (for windows and iframes)
console.log(event.ports); // MessagePort, if any
console.log(event.source); // who sent the message
};A simple analogy:
Imagine
postMessage()is a postal service between offices. Every office (a JS thread) works in isolation. When one wants to hand data to another, it drops a letter (the message) into the mailbox (postMessage). The other office reads the letter when it has time (onmessage).
Summary:
| What it does | Explanation |
|---|---|
postMessage() | Sends a message between threads and contexts |
| Works asynchronously | Does not block the code |
| Passes data by copy | Through structured clone |
| Optimisation | Transferable objects, no copying |
| Two way channel | Both threads can send and receive |
| Where it is used | Web Workers, iframes, windows, Service Workers |
Common mistakes
- Assuming the other side receives the same object. It receives a copy, so mutations there are invisible to the sender.
- Using a
bufferafter it was handed over as transferable. It is already detached, because ownership moved to the other thread. - Not checking
event.origininsidewindow.addEventListener('message', ...). Any page can send a message, and skipping that check is a security hole. - Passing
'*'as the targetOrigin inpostMessage(data, '*')for sensitive data. Name the concrete origin instead. - Sending functions, DOM nodes or class instances with methods. Structured clone does not support them and throws
DataCloneError. - Expecting
postMessage()to return something or to answer synchronously. It returnsundefined, and the reply arrives as a separate event.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.