Mutex Pattern in JavaScript
Prevent concurrent access to shared resources using a promise-based mutex.
The Problem
Two or more async operations race for the same resource — e.g. double-clicking a button triggers two API calls, or concurrent writes corrupt state.
The Mutex
class Mutex {
#queue: Promise<void>[] = [];
acquire(): Promise<() => void> {
let release!: () => void;
const prev = this.#queue[this.#queue.length - 1] ?? Promise.resolve();
const next = new Promise<void>((resolve) => (release = resolve));
this.#queue.push(next);
return prev.then(() => release);
}
}
Usage
const mutex = new Mutex();
async function save(data: unknown) {
const release = await mutex.acquire();
try {
await fetch("/api/save", { method: "POST", body: JSON.stringify(data) });
} finally {
release();
}
}
Calls to save now queue up — only one runs at a time.
Why Not a Simple Flag?
let busy = false;
async function save(data: unknown) {
if (busy) return; // silently drops the call
busy = true;
await fetch("/api/save", { method: "POST", body: JSON.stringify(data) });
busy = false;
}
A flag either drops calls or lets them through. A mutex queues them — no call is lost, no call overlaps.
Cheatsheet
| Flag | Mutex | |
|---|---|---|
| Drops calls | yes | no |
| Queues calls | no | yes |
| Guarantees order | no | yes |