Skip to the solution
HardDSA7 min read

How does Atomics.waitAsync() enable non-blocking waits on SharedArrayBuffer?

1.1k views

The problem

Question presented to candidate: "Atomics.wait() blocks the calling thread until another thread calls Atomics.notify() on the same location. Why can that be a problem, and how does Atomics.waitAsync() solve it? Walk through what its return value actually looks like, and what happens on Node's main thread specifically versus a browser's main thread."

What a strong answer should cover:

  • Atomics.wait(typedArray, index, value, timeout) blocks the calling thread's execution entirely until another thread calls Atomics.notify() on that exact index, or the timeout elapses β€” nothing else on that thread runs while blocked, including timers and I/O callbacks.
  • Atomics.waitAsync() returns synchronously and immediately, shaped { async: boolean, value }. If the current value already differs from the expected one, it returns { async: false, value: "not-equal" } with no promise involved. Otherwise it returns { async: true, value: aPromise } that resolves to "ok" or "timed-out".
  • In BROWSERS, Atomics.wait() throws a TypeError on the main/UI thread specifically β€” it is only legal on a Worker there, since blocking the UI thread would freeze the page. Node.js has no such restriction: Node's "main thread" is not a UI thread, so Atomics.wait() is legal there and simply blocks it β€” verified directly, not assumed.
  • waitAsync()'s pending promise does not block the event loop while pending β€” other code, timers, and I/O keep running normally, verified by a real interleaved setTimeout that fires before the promise resolves.
  • SharedArrayBuffer plus Atomics is the actual low-level primitive underneath higher-level constructs like WebAssembly threads and hand-rolled worker pools that need to coordinate without message-passing overhead.

Clarifying questions expected:

  • "Does this run in a browser or in Node.js?" β€” the main-thread-blocking restriction on Atomics.wait() is browser-specific, not a JavaScript-spec-wide rule, and answering as if it always throws is a common but real mistake worth naming explicitly.
  • "Is cross-origin isolation (COOP/COEP headers) set up?" β€” SharedArrayBuffer is disabled by default in browsers without those headers, which silently makes this whole API surface unavailable.

Code / implementation expected: Yes β€” a real, non-blocking waitAsync() call verified to return immediately, plus a real cross-thread worker_threads example proving the promise resolves only after a genuine Atomics.notify() from another thread.

  • atomics
  • sharedarraybuffer
  • concurrency

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

7 min read Β· 7 sections

Run the code

Edit the code and run it here. Each console line with an expected value becomes a test.

JavaScriptNon-blocking waitAsync() with a same-thread simulated notifier (works anywhere, including this playground) (run directly)
JavaScriptReference: genuine cross-thread coordination via node:worker_threads, a real second thread calling Atomics.notify() (run with: node file.js -- worker_threads is Node-only, not available in the browser playground)
import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads";

if (isMainThread) {
  const sab = new SharedArrayBuffer(4);
  const i32 = new Int32Array(sab);
  i32[0] = 0;

  const t0 = Date.now();
  const worker = new Worker(new URL(import.meta.url), { workerData: { sab } });

  console.log("[main] calling Atomics.waitAsync -- must NOT block");
  const { async, value } = Atomics.waitAsync(i32, 0, 0, 5000);
  console.log("[main] waitAsync returned immediately, async:", async);

  value.then((result) => {
    console.log(`[main] promise resolved with "${result}" after ${Date.now() - t0}ms`);
    worker.terminate();
  });

  console.log("[main] this line runs right after waitAsync");
} else {
  const { sab } = workerData;
  const i32 = new Int32Array(sab);
  const start = Date.now();
  while (Date.now() - start < 300) { /* real work, inside the WORKER only */ }
  Atomics.store(i32, 0, 1);
  const woken = Atomics.notify(i32, 0, 1);
  console.log("[worker] Atomics.notify() woke", woken, "waiter(s)");
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 124 of 165 in JavaScript

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

Up nextHard

Why can pushing a single non-numeric value into a large numeric array tank its performance in V8, and how would you avoid it?

Continue