Web Locks API: JavaScript Mutual Exclusion Explained

The Web Locks API is a native browser interface that enables asynchronous JavaScript code across multiple browser contexts—such as tabs, windows, iframes, and Web Workers—to coordinate resource access using mutual exclusion. By allowing scripts to acquire, hold, and release named locks, the API prevents race conditions and data corruption when multiple execution threads or tabs interact with shared resources like IndexedDB, Cache API, or BroadcastChannel.

The Need for Mutual Exclusion in Browsers

JavaScript in the browser runs on a single-threaded event loop per execution context, but modern web applications often run across multiple contexts simultaneously. When a user opens multiple tabs of the same web application, each tab executes its own JavaScript thread while sharing the same underlying origin storage (such as IndexedDB or LocalStorage).

Without proper synchronization, concurrent writes or read-modify-write sequences from separate tabs can lead to severe race conditions. Traditionally, developers attempted to manage this with workarounds like localStorage polling or SharedWorker coordination, which were inefficient, fragile, and prone to deadlocks when tabs crashed. The Web Locks API provides a built-in, browser-level solution designed specifically for this problem.

How the Web Locks API Operates

The API is accessed via the navigator.locks interface. Its primary method is navigator.locks.request(), which queues a request for a lock identified by a unique string name.

navigator.locks.request('user_profile_sync', async (lock) => {
  // Critical section: only one context runs this code at a time
  const data = await readProfileData();
  const updatedData = processUpdates(data);
  await saveProfileData(updatedData);
  // The lock is automatically released when the returned Promise settles
});

The lifecycle of a Web Lock consists of three main phases:

  1. Request: The script requests a lock with a specific name. If no conflicting lock is held, it is granted immediately. If a conflicting lock exists, the request waits in a FIFO queue.
  2. Execution: Once acquired, the browser invokes the provided callback function, passing the lock object. The callback represents the “critical section” of the code.
  3. Release: The lock is automatically released as soon as the callback’s returned Promise resolves or rejects. If the tab or worker crashes, the browser immediately releases the lock, avoiding permanent deadlocks.

Lock Modes: Exclusive vs. Shared

The Web Locks API supports two modes of operation:

// Requesting a shared lock for reading
await navigator.locks.request('user_profile_sync', { mode: 'shared' }, async (lock) => {
  const data = await readProfileData();
  console.log('Read profile safely:', data);
});

Advanced Options and Capabilities

The Web Locks API includes features to handle edge cases and optimize performance: