Your Server-Sent Events board is right for updates, but patrol tablets must occasionally send a trail closure. What do you do?
answer
- the stream flows one way only
- upstream is its own request
- verdict in the response, state on the stream
- one round trip per client message
- rate and latency decide the switch
basics
~20 sKeep the one-way stream for updates and send each upstream message as an ordinary HTTP request to the same service. That pairing holds while upstream traffic is occasional; at continuous rates or tight per-message latency, a duplex socket wins.
solid answer
~40 sHold the stream for everything flowing down and use a normal request for everything flowing up: the tablet keeps its `GET` on `text/event-stream` open, and a closure is a `POST` to the same service. The immediate verdict — accepted or rejected — comes back in that request's own response, and the resulting state change is published on every open stream so the lobby screens and phones see it too. What you pay is one request round trip per upstream message and its authorization, plus the fact that the two paths are independent, so an upstream message is not ordered against the events coming down. That is a fine price a few times a shift, and the wrong price for continuous input.
code
http · 10 linesPOST /board/trail-status HTTP/1.1
Host: lifts.example
Content-Type: application/json
{"trail":"north-bowl","state":"closed","by":"patrol-7"}
HTTP/1.1 200 OK
Content-Type: application/json
{"accepted":true,"effectiveAt":"2026-01-18T09:41:00Z"}go deeper
Remember the shape: the stream only flows down, so anything the device sends is an ordinary request to the same service, and the resulting change comes back on the stream.
Explain the full round trip — request up, verdict in its response, state change published to every open stream — and name what the extra path costs per message.
Give the threshold and defend it: upstream rate against downstream rate, the per-message latency budget, and what you do about duplicates and the missing ordering between the two paths.
Decide whether a product's upstream pattern justifies a second transport across the organisation, knowing each one adds an authorization story, a reconnect implementation and a capacity model to maintain.
## The shape that actually works The instinct on hearing "but the tablets also send" is to abandon the one-way stream and upgrade everything to a duplex socket. That is usually an overcorrection. The working shape is two paths that do different jobs: 1. **Down:** one long-lived `GET` per device, asking for `text/event-stream`, on which the service writes every lift and trail change as it happens. 2. **Up:** an ordinary request per client message — a `POST` carrying the closure, authorized exactly like every other request the service handles. 3. **Back down again:** the accepted change is published on every open stream, so all the other devices learn about it through the same channel they learn everything else through. That third step is the part teams forget, and it is what makes the pairing coherent rather than two half-systems. Every device, including the one that sent the change, ends up with the same state from the same source. ## Where the reply belongs There are two plausible places for the answer to an upstream message, and they are not alternatives — use both: - **The request's own response** carries the immediate verdict: accepted, rejected, and why. The sender needs that synchronously, and it is the natural place for validation errors. - **The stream** carries the resulting state change, to every device including the sender. Sending it *only* on the stream makes the sender wait for its own broadcast to find out whether its tap worked, which reads as latency and fails badly if the stream is momentarily re-opening. ## What the second path costs - **A round trip per message.** Every upstream message pays a request and a response, where a socket write pays neither. - **Authorization per message**, which is also a benefit — the same rule as every other request, applied every time, with no special case. - **Two independent paths.** The upstream request and the downstream stream are separate exchanges. There is no ordering guarantee between "my closure was accepted" and events already in flight on the stream, so design state as something the server settles rather than something the client and server each track. - **Duplicate suppression is yours.** A retried request can arrive twice; give upstream messages an identity the service can deduplicate on if the action is not naturally repeatable. ## Where the pairing breaks | Upstream pattern | Example on the mountain | Verdict | |---|---|---| | A few messages per shift | A patrol tablet marks a trail closed | Requests, easily | | A handful per session | A screen switches which mountain it shows | Requests | | A message per human action | An operator confirms a lift hold | Requests | | A message every few hundred milliseconds | Dragging a live grooming route across the map | Socket | | Continuous per-input traffic | Free-hand drawing a closure boundary | Socket | The useful rule of thumb: if the upstream rate comes within an order of magnitude of the downstream rate, you are no longer adding a side channel to a one-way feed — you are building a duplex protocol out of requests, and you should use one that already exists. ## The latency question, stated properly The cost of the extra path is not "HTTP is slow". It is that each upstream message is its own exchange, so each one waits for a full round trip before the sender knows anything. For an action a human takes and then waits on, that round trip is invisible next to the human. For a stream of positions produced faster than a round trip completes, the round trips stack up and the interaction feels wrong no matter how fast the service is. That is the real threshold, and it is about the *pattern* of upstream traffic rather than its volume in bytes. ## What an interviewer is listening for - That you did not treat "the client sometimes sends" as automatic proof that a socket is required. - That you know where the verdict goes versus where the state change goes. - That you can name a concrete threshold — rate, and per-message latency budget — instead of saying "it depends". - That you noticed the two paths are independent, and said what you do about ordering and duplicates rather than assuming they are free.
- Should the result of an upstream request come back in that response or on the stream?Both, for different things. The request's own response carries the immediate verdict — accepted or rejected, with the reason — because the sender is waiting on it. The stream carries the resulting state change to every device, the sender included. Relying on the stream alone makes the sender wait for its own broadcast, which stalls visibly if the stream happens to be re-opening.
- How do you keep upstream messages ordered against the events coming down?You do not, because they are independent exchanges. Design for it instead: let the server be the sole authority on state, stamp each published change so a client can tell old from new, and give upstream messages an identity the service can deduplicate on. A client that maintains its own parallel copy of the state will eventually disagree with the stream.
- At what point does this pairing stop being acceptable?When upstream messages become continuous rather than occasional — dragging, drawing, per-frame telemetry, anything produced faster than a round trip completes. A practical trigger is upstream rate reaching within an order of magnitude of downstream rate, or a per-message latency budget too tight for a request and its response.
saying these in an interview costs you the question
- Treats any upstream traffic at all as proof that a socket is required
- Says the client can write its message into the open stream response
- Assumes upstream requests are ordered against events arriving on the stream
- Returns the verdict only on the stream, leaving the sender waiting
- Keeps the pairing for per-input traffic like dragging or drawing