Representative interview topic

Frontend Interview: Design Safe Cross-Tab Sync with BroadcastChannel

FrontendHard
Offer.cc Editorial TeamPublished Updated

Question

An admin console lets a user open several tabs. After the user logs out, changes a theme, or updates notifications in one tab, the others should converge quickly. Refreshes, sleeping tabs, duplicate messages, and browsers without BroadcastChannel must not corrupt the final state. Design the protocol, recovery, fallback, and verification metrics.

Prompt and context

An admin console lets a user open several tabs. After the user logs out, changes a theme, or updates notifications in one tab, the others should converge quickly. Refreshes, sleeping tabs, duplicate messages, and browsers without BroadcastChannel must not corrupt the final state. Design the protocol, recovery, fallback, and verification metrics.

This is a browser-coordination question for frontend, web-platform, and frontend-system-design roles. Tab count, sync delay, and event types are interview assumptions, not browser guarantees. Focus on BroadcastChannel's same-origin and storage-partition boundaries, the consequences of non-persistent messages, the event-versus-state choice, and when to combine it with localStorage, IndexedDB, SharedWorker, or Web Locks.

What the interviewer is testing

First, can you state the API boundary? BroadcastChannel sends messages among windows, tabs, frames, and workers that share an origin and a communicable storage partition. It is not a cross-origin transport, durable queue, or distributed-consistency service.

Second, can you design an idempotent protocol? Messages can be duplicated, the sender does not receive its own broadcast, and a receiving page may close or sleep. A message saying only “the theme changed” without a version and reread path leaves state permanently stale.

Third, can you separate notifications from the source of truth? Logout can broadcast invalidation, theme changes can be persisted and then announced, and a notification update should make receivers reread an authoritative cache rather than treating a full list in the message as truth.

Finally, can you handle lifecycle and fallback: close channels, keep tokens out of messages, detect capability, use storage events or server rereads, and measure that synchronization creates no loop or memory leak?

Questions to clarify first

  • Is the payload a one-time event, current state, or replayable history? That determines whether a persistent version is required.
  • Is the scope one origin, an iframe under the same top-level site, or different subdomains? A storage partition can prevent communication even when origins look related.
  • Can one message be lost? Logout, permission revocation, and edit conflicts need different recovery guarantees.
  • Where is the source of truth: server, IndexedDB, localStorage, an in-memory cache, or a Service Worker?
  • Must one tab be the only writer for refreshes, migrations, or batch work? BroadcastChannel does not provide a lock.
  • What is the browser matrix, including private mode, background freezing, and expected tab count?
  • Could a message contain profile data, permissions, or a token? If so, replace it with a non-sensitive invalidation signal.

A 30-second answer

“I would use BroadcastChannel as a low-latency notification bus, not as a durable state source. Each message carries a protocol version, event type, monotonic sequence or state version, and trace ID. Receivers validate the shape, apply the event idempotently, and reread localStorage, IndexedDB, or the server when a version gap appears. Logout sends invalidation, theme writes persistent preferences, and notification changes trigger a reread. If the API is unavailable, use storage events or polling; if one writer is required, use Web Locks or server coordination. Close every channel on teardown and measure latency, lost-message recovery, and duplicate handling.”

Step-by-step answer

Step 1: Define messages and authoritative state

Assign a source of truth per feature. Theme and language are preferences that can live in persistent settings. Notifications and permissions should be reread from the server or a local cache. Logout is a session-invalidation signal; never put an access token in a message. Broadcast only non-sensitive type, version, entityKey, updatedAt, or traceId.

MDN explains that BroadcastChannel supports bidirectional communication among same-origin browsing contexts and workers, while the application defines the message protocol; the platform provides no negotiation. Versioning, unknown-event handling, and field validation are application responsibilities.

Step 2: Handle duplicates, ordering, and loss

Maintain the last processed version for each state domain. Ignore a message whose version is older or equal; a contiguous newer version can trigger one reread; a jump marks a gap and starts a snapshot sync. If the business exposes events without versions, use a deduplication ID and a bounded processed set, while admitting that this cannot prove an intermediate event was not lost.

Do not assume delivery. The sender does not receive its own message, and a newly opened or sleeping tab can miss it. On startup, load persistent state or a server snapshot before subscribing; the broadcast only reduces freshness latency.

Step 3: Choose persistence and coordination together

localStorage fits small preferences and can trigger a storage event; it is not a high-throughput database or a place for tokens. IndexedDB fits larger local caches and versioned snapshots. A Service Worker can participate in background sync, but it should write results to recoverable storage.

BroadcastChannel solves notification to multiple contexts, not single-writer coordination. If one tab alone may refresh a cache, run a migration, or submit a batch, use Web Locks. Without the lock, include a version condition and let the server arbitrate. A SharedWorker fits shared connections or centralized state, but adds lifecycle and compatibility complexity.

Step 4: Build safe reception and fallback

Accept only allowlisted event types, cap message size, and reject unknown versions or invalid schemas. Never place an access token, full profile, or executable HTML in a message. On logout, clear sensitive local caches and navigate to sign-in; on a theme event, update the UI but trust the persisted setting.

If capability detection fails, use storage events for small state. If that is unavailable or partitioned, reread on focus, poll briefly, or use server push. A fallback must be safe to repeat; lack of broadcast cannot become permanent divergence.

Step 5: Manage resources and verify behavior

Remove listeners and call close() when a component or page is destroyed. Do not create a new channel on every render. Use a namespace so unrelated applications do not subscribe to the same name. Include a source label and trace ID so a receiver does not rebroadcast the same message in a loop.

Test multi-tab end-to-end delay, duplicates and reordering, resume after sleep, the initial snapshot after refresh, storage fallback, storage-partition isolation, channel closure, oversized messages, loops, and user switching. Track handler success, version gaps, rereads, discarded duplicates, recovery time, and channels left open.

Model high-quality answer

“I would define BroadcastChannel as a notification bus, not a queue. Logout carries only a session-invalidation type and version. A theme update writes localStorage and broadcasts the new preference version. A notification update broadcasts an entity key and version, so each receiver rereads from the server or IndexedDB. Messages contain no token or complete user data.

Each message has a protocol version, monotonic state version, and trace ID. Receivers reject unknown shapes, discard processed or older versions, and fetch a snapshot when they see a gap. Startup loads a snapshot before subscribing because a new or sleeping tab can miss a broadcast, and the sender does not receive its own message.

If cache refresh needs one writer, I would add Web Locks; BroadcastChannel alone cannot stop two tabs from writing concurrently. Unsupported browsers use storage events for small preferences and focus-time rereads or short polling for other data. Teardown removes listeners and closes the channel. Metrics cover delay, gaps, duplicates, recovery time, and leaked resources. This separates low-latency notification, recoverable state, and browser compatibility.”

Common failure modes

  • Treating broadcast as a durable queue → new or sleeping tabs miss events → load a snapshot first and use messages as hints.
  • Putting tokens or full state in messages → expands sensitive-data exposure and version conflicts → send only non-sensitive events, keys, and versions.
  • No version or deduplication ID → duplicates and reordering roll the UI backward → use monotonic versions, idempotent handling, and gap rereads.
  • Assuming same-origin means guaranteed connectivity → storage partitioning can isolate contexts → test the actual boundary and provide fallback.
  • Using BroadcastChannel as a lock → two tabs can still write concurrently → use Web Locks or conditional server writes.
  • Creating a channel every render → listeners and resources leak → keep a stable instance, remove listeners, and close it.
  • Rebroadcasting every received message → creates a loop → carry source/trace IDs and send only on state change.
  • Treating storage events as a complete replacement → they cover only some writes and not the same window → document the difference and add rereads.

Follow-up questions

Follow-up 1: Two tabs edit the same form. How do you avoid overwriting?

Broadcast that the resource version changed; do not overwrite the local draft. Submit a base version, let the server reject stale conditional writes, and show a conflict for user merge. A local preference can use Web Locks for serialized writes.

Follow-up 2: A tab wakes after sleeping for 20 minutes. How does it catch up?

Reread a server or IndexedDB snapshot on resume, compare state versions, and then process any incremental signal. Do not rely on the last broadcast. If the snapshot is unavailable, mark the state stale and require revalidation or show a clear expired state.

Follow-up 3: Pages on different subdomains need to communicate. Does BroadcastChannel solve it?

Do not assume it does. BroadcastChannel is constrained by origin and storage partition. Cross-origin communication needs an explicit postMessage window relationship or server coordination, with origin, schema, and permission checks; do not weaken the security boundary just to share a channel.

Follow-up 4: Only one tab should maintain a WebSocket. How would you design it?

BroadcastChannel can share connection state and data but cannot elect a leader. Prefer Web Locks for the holder, with other tabs subscribing to its broadcasts; re-elect after the holder closes or loses the lock. If unavailable, use a server lease or allow multiple connections with server-side deduplication and an explicit cost trade-off.

Public sources

Related questions