Representative interview topic

How do you coordinate shared work across tabs with the Web Locks API?

FrontendHard
Offer.cc Editorial TeamPublished Updated

Question

A web app may have several tabs open, but only one should refresh shared cache data and run synchronization at a time. Design coordination with the Web Locks API and explain scope, queuing, ifAvailable, AbortSignal, tab crashes, and unsupported browsers.

1. Prompt

Several same-origin tabs may refresh an IndexedDB cache and synchronize with the server at once. Design a coordinator that lets at most one context synchronize while others wait, skip, or recheck after release. Cover tab closure, network errors, and browsers without Web Locks.

2. Constraints and clarifications

  • A lock name is shared across same-origin windows and workers.
  • A lock protects only the asynchronous callback’s coordination scope; it is not a database transaction or server-side concurrency control.
  • Synchronization may take time, so it needs cancellation, retries, and a version or result broadcast.
  • Distinguish waiting for a lock, probing immediately, and abandoning a request.

3. Core approach

Call navigator.locks.request(name, options, callback) to request a named lock. The lock is released after the callback Promise settles, so the callback must contain the complete read, sync, and state-write flow. A default request queues; ifAvailable: true calls back with null when busy; signal can cancel a request that is still waiting.

The lock manager releases a lock when its owning context terminates, but correctness must not rely only on tab crash cleanup. Sync records should carry a version, lease, or idempotency key, and the server should still validate writes. Other tabs can learn a new version through BroadcastChannel or by rereading IndexedDB.

4. Reference implementation

javascript
const LOCK = "shared-cache-sync";

async function runSync(signal) {
  return navigator.locks.request(LOCK, { signal }, async (lock) => {
    if (!lock) return { status: "busy" };
    const current = await readSyncVersion();
    if (await isFresh(current)) return { status: "fresh" };
    const result = await fetchAndWriteCache({ signal, baseVersion: current });
    await publishVersion(result.version);
    return { status: "updated", version: result.version };
  });
}

async function trySyncWithoutWaiting() {
  return navigator.locks.request(
    LOCK,
    { ifAvailable: true },
    (lock) => lock ? runOneSync() : { status: "busy" },
  );
}

5. Correctness and failure handling

The lock provides mutual exclusion among same-origin contexts for the named resource; it does not roll back a network request or database write. Reread the version before a conditional update. If the network fails, throw so the lock releases and a later request can retry. Do not store a “we own the lock” boolean outside the callback.

When an AbortSignal cancels a waiting request, callers should distinguish cancellation from business failure. For long waits, show that another tab is synchronizing or skip the work; after release, reread the version to avoid duplicate refreshes. A BroadcastChannel notification is an optimization; IndexedDB or the server remains authoritative.

6. Follow-ups and traps

  • Web Locks coordinates same-origin contexts only; it cannot lock another site, a server process, or a database row.
  • ifAvailable does not queue. A busy request receives null, which is a normal outcome.
  • steal: true breaks existing queue semantics and should be limited to explicitly disposable work with version checks.
  • Without the API, BroadcastChannel plus IndexedDB can provide best-effort coordination, not the same mutual-exclusion guarantee; the server still needs deduplication.

7. Further reading

Compare Web Locks, IndexedDB transactions, Service Worker messages, and BroadcastChannel: locks provide cross-context exclusion, transactions provide single-database atomicity, and channels provide notification. Cross-device or cross-user exclusion belongs on server leases, idempotent APIs, or database constraints.

8. Interview scoring points

Can state the lock scope

The candidate should say that a named lock coordinates same-origin windows and workers, releases when the callback Promise settles, and cannot replace server or database concurrency control.

Can distinguish request modes

They should explain default queuing, immediate ifAvailable probing, and abandoning a wait with AbortSignal.

Can handle crashes and duplicates

They should use versions, idempotency keys, and conditional writes, explaining that tab crash cleanup does not roll back business writes.

Can give an honest fallback

They should provide a BroadcastChannel/IndexedDB best-effort path and state that mutual exclusion is weaker while server safeguards remain mandatory.

Public sources

Related questions