Representative interview topic

Frontend interview: How would you evaluate WebSocketStream backpressure and fallback?

FrontendHard
Offer.cc Editorial TeamPublished Updated

Question

A collaborative page receives high-frequency events and sends edits to a server. Evaluate whether WebSocketStream fits, explain how Streams backpressure, close and cancellation prevent message buildup, and design a fallback for unsupported browsers.

Prompt and scope

A collaborative page receives high-frequency events and sends edits to a server. Evaluate whether WebSocketStream fits, explain how Streams backpressure, close and cancellation prevent message buildup, and design a fallback for unsupported browsers.

WebSocketStream is an experimental, non-standard Web API. It exposes a connection as ReadableStream and WritableStream objects, allowing Streams backpressure to regulate reading and writing. MDN explicitly recommends checking browser compatibility before production use. The interview is about stream-protocol design and risk judgment.

What the interviewer is testing

Cover ReadableStream and WritableStream lifecycles, how backpressure prevents unbounded queues, reader and writer locks, the difference between abort and close, message ordering and idempotency, heartbeats and reconnects, Worker use, capability detection, and fallback.

A 30-second answer

“I first confirm that WebSocketStream is not a broadly standardized capability and enable it only after feature detection. I manage readable and writable independently, let backpressure propagate instead of buffering an unbounded array, and make close, cancellation, and network errors observable states. AbortSignal carries component lifetime into asynchronous work. Production keeps a classic WebSocket adapter with the same message protocol, bounded queues, reconnect backoff, and duplicate suppression.”

Step-by-step solution

Step 1: Confirm capability and boundary

Check whether the runtime exposes WebSocketStream before enabling it. It is experimental and non-standard, so support in one Chrome build does not imply support in every browser, WebView, or enterprise environment. A failed capability check selects the stable implementation directly.

Step 2: Open the connection and streams

Construct WebSocketStream and await the opened promise to obtain readable, writable, protocol, and extension information. Consume the readable side through a reader and write through a writer; report connection shutdown through the closed promise.

js
const socket = new WebSocketStream(url);
const { readable, writable } = await socket.opened;
const reader = readable.getReader();
const writer = writable.getWriter();

Step 3: Use backpressure

Do not push every event into a normal array. Let a downstream TransformStream or WritableStream highWaterMark bound the queue, await writer.ready before sending, and call read at the consumer’s pace. Backpressure regulates the client stream; the server still needs protocol-level rate limits.

Step 4: Define message ordering

Give edits a sequence number, document version, and idempotency key. After reconnecting, do not assume the last message in the old stream was persisted. Request a gap or a versioned snapshot from the server before applying new events, and make both rendering and transport layers recognize duplicates.

Step 5: Handle cancellation and close

When a user leaves or changes documents, stop reads and writes, then close or cancel the relevant streams. AbortSignal connects component lifetime to asynchronous work. Normal close, active cancellation, protocol error, and network loss should be distinct states so reconnect logic applies only to real network failures.

Step 6: Protect memory and the UI

Separate parsing, validation, and rendering; move decoding or batch processing to a Worker when needed. Set maximum message size, pending-event count, and batch time. Above the threshold, pause subscription, merge or drop reconstructible intermediate events, or request a server snapshot instead of accumulating indefinitely.

Step 7: Design reconnect and recovery

Use exponential backoff with jitter and include the last confirmed document version. After the server returns missing events or a snapshot, resume the live stream. Client-generated idempotency IDs prevent a disconnected write from being submitted twice.

Step 8: Implement fallback and observability

Use classic WebSocket as the default fallback with the same message envelope and state machine. Record capability results, opened and closed latency, queue depth, backpressure wait, reconnect count, dropped events, and snapshot recovery rate. Break them down by browser, network type, and page version before expanding the experiment.

Trade-offs and boundaries

WebSocketStream or WebSocket

WebSocketStream provides stream backpressure and Promise-based lifecycle signals, but support and standardization are limited. Classic WebSocket is widely available yet requires the application to bound onmessage work and the send queue. One protocol abstraction can support both transports.

Drop events or request a snapshot

Reconstructible events such as cursor movement can be merged or dropped when the queue is full. Document edits cannot be silently dropped; pause consumption and request a versioned snapshot. The choice depends on commutativity, ordering, and server replay support.

Main thread or Worker

Small messages and low-rate rendering fit the main thread. High-rate binary decoding, compression, and batch merging can move to a Worker. A Worker does not remove the total memory limit, so transport queues and cancellation still need explicit bounds.

Failure drills and evolution

Browser does not support the API

Turn off the capability branch and verify that classic WebSocket connects, consumes the same messages, and reports compatibility metrics.

The consumer slows down

Artificially slow rendering and verify that backpressure wait increases, the queue remains bounded, and a snapshot recovery path triggers at the threshold instead of crashing.

The connection drops mid-write

Disconnect immediately after sending an edit. Verify that the idempotency ID, last confirmed version, and reconnect backoff restore state without applying the edit twice.

Common mistakes and follow-ups

Mistake 1: Assuming Streams solve server throttling

Follow-up: What does backpressure control? It controls production and consumption inside the client stream; it does not replace server quotas, connection limits, or business batching.

Mistake 2: Treating close, cancel, and abort as identical

Follow-up: What happens on component unmount? Record normal close, read cancellation, and asynchronous abort separately so reconnect logic targets actual network faults.

Mistake 3: Shipping only WebSocketStream

Follow-up: What about a WebView or older browser? A failed capability check selects the classic WebSocket adapter, while the message protocol and state machine stay the same.

Deeper follow-ups and model answers

Why await writer.ready?

It lets the sender wait for WritableStream capacity under backpressure instead of creating unbounded promises or arrays. Server rate limits and message-size limits are still required.

When should the client request a snapshot?

When the event queue exceeds its bound, a sequence gap appears, or the client cannot confirm a continuous version, a versioned snapshot is safer than guessing event order.

How do you roll out an experimental API?

Bucket by capability, browser version, and page version, then enable a small percentage. Compare queue depth, disconnect recovery, error rate, and render latency; disable the flag and return to classic WebSocket when metrics regress.

Public sources

Related questions