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.
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.