SharedArrayBuffer

TL;DR / SharedArrayBuffer provides a fixed-length block of raw memory that multiple threads (main thread and workers) can read and write simultaneously, enabling true shared-memory concurrency in JavaScript.

How It Works

 ┌──────────────┐                        ┌───────────────┐
 │ Main Thread  │                        │ Worker Thread │
 └──────────────┘                        └───────────────┘
         │                           read/write  │
         │─┐                            ┌────────│
         │ │                            │        │
      rea│/write──────────────────────┐ │        │
         │ │   │  SharedArrayBuffer   │ │        │
         │ └──→│   (shared memory)    │←┘        │
         │     └──────────────────────┘          │
         │                                       │
         │                                       │
         │       Atomics.wait / notify           │
         ↓                                       ↓
 ┌──────────────┐                        ┌───────────────┐
 │  Int32Array  │                        │  Int32Array   │
 │     view     │                        │     view      │
 └──────────────┘                        └───────────────┘

Edit diagram

JavaScript has historically been single-threaded, with Web Workers providing concurrency only through message passing. postMessage copies data between threads using the structured clone algorithm — adequate for small payloads, but prohibitively expensive when threads need to share megabytes of data or coordinate at high frequency. SharedArrayBuffer (SAB) solves this by providing a raw memory region that multiple threads can access without copying.

Creating and Sharing

You allocate a SharedArrayBuffer on any thread: const sab = new SharedArrayBuffer(1024) creates a 1KB block. To use it in a worker, you send it via postMessage. Unlike a regular ArrayBuffer, a SharedArrayBuffer is not transferred (not detached from the sender) — both threads retain access to the same underlying memory. The sender and receiver both hold references to the identical byte range.

To read and write structured data, you create typed array views over the buffer: new Int32Array(sab), new Float64Array(sab, offset, length), etc. Multiple views can overlap or partition the same buffer. Each thread creates its own view, but the underlying memory is shared.

The Atomics API

Shared memory without synchronization is a recipe for data races. Two threads writing to the same memory location concurrently produce undefined results because JavaScript does not guarantee the ordering of memory operations across threads. The Atomics object provides atomic operations — reads and writes that are guaranteed to complete as indivisible units, and memory barriers that enforce ordering.

Atomics.load(view, index) reads a value atomically. Atomics.store(view, index, value) writes atomically. Atomics.add(view, index, value) performs an atomic read-modify-write. Atomics.compareExchange(view, index, expected, replacement) is the foundation for building locks: it writes the replacement only if the current value matches the expected value, returning the actual value it found. This is the classic CAS (compare-and-swap) primitive used in lock-free data structures.

Atomics.wait(view, index, expectedValue) blocks the calling thread until the value at the given index changes from the expected value — this is how you implement a mutex or condition variable. Atomics.notify(view, index, count) wakes threads that are blocked on wait. Note that Atomics.wait cannot be called on the main thread (it would freeze the UI); it is restricted to worker threads. Use Atomics.waitAsync for a non-blocking, Promise-based alternative usable on any thread.

Memory Model

JavaScript's shared memory model follows a well-defined specification based on sequential consistency for data-race-free programs (SC-DRF). If you access shared memory only through Atomics operations, the behavior is predictable and matches the intuitive expectation of sequential execution. If you use non-atomic operations on shared memory (regular typed array access), the ordering across threads is undefined — the compiler and CPU may reorder reads and writes in ways that produce surprising results.

In practice: use Atomics for any data that multiple threads read and write. Use non-atomic access only for data that is written by one thread and read by another after a synchronization point (e.g., after Atomics.notify / Atomics.wait establishes a happens-before relationship).

Security Requirements: Cross-Origin Isolation

After the Spectre and Meltdown CPU vulnerabilities were disclosed, browsers restricted SharedArrayBuffer because shared memory combined with high-resolution timers could be used for side-channel attacks. To use SAB, your page must be cross-origin isolated, which requires two HTTP headers:

Cross-Origin-Opener-Policy: same-origin — prevents your page from sharing a browsing context group with cross-origin pages (breaks window.opener references).

Cross-Origin-Embedder-Policy: require-corp — ensures all subresources either are same-origin or explicitly opt into being loaded cross-origin via Cross-Origin-Resource-Policy headers.

These headers create a secure context where the browser can safely allow shared memory. Without them, new SharedArrayBuffer() throws a TypeError.

Use Cases

High-performance applications benefit most: audio/video processing pipelines where workers operate on shared sample buffers, game engines sharing physics state between a simulation thread and the render thread, machine learning inference distributing tensor computations across workers, and real-time collaborative editors synchronizing document state with minimal latency.

Gotchas

  • Cross-origin isolation breaks third-party embeds — setting COEP: require-corp means every iframe, image, and script must include CORS headers. This breaks many third-party widgets (ads, analytics, social embeds) unless they opt in with crossorigin attributes and proper server headers.
  • Atomics.wait blocks the thread — calling it on the main thread throws TypeError. On worker threads, it blocks synchronously, which can cause deadlocks if not carefully managed.
  • No garbage collectionSharedArrayBuffer is raw bytes. There is no structured data, no references, no garbage collection. You must manually manage offsets, lengths, and data layout. This is systems programming inside JavaScript.
  • Data races with non-atomic access — reading or writing shared memory without Atomics across threads produces undefined behavior. The spec explicitly allows any value to be observed, including values that were never written.
  • Safari support was late — Safari only enabled SharedArrayBuffer in version 15.2 with cross-origin isolation requirements, and behavior around COEP/COOP headers can differ subtly across browsers.