A crane console sends a command over a WebSocket and needs to know which reply answers it - what does the protocol give it?
answer
- the wire pairs nothing for you
- two independent flows of messages
- no identifier, no status, no report
- echo a field you invented yourself
- outstanding table plus a timeout
basics
~10 sNothing. A WebSocket carries whole messages, not calls: there is no request identifier, no reply field and no delivery report. Request-and-response over a socket is an application convention both ends must invent and agree.
solid answer
~40 sThe protocol gives each direction an independent flow of whole messages and defines no pairing between them. A message carries no identifier, no reference to another message, no status and no acknowledgement, so nothing on the wire says this reply answers that command - or that the command was ever processed. Both ends therefore have to agree an envelope: a field the requester generates and the responder echoes, a table of outstanding requests keyed by it, a timeout per entry because nothing else will ever tell you a command was dropped, and a rule for a message matching no entry, since the peer may also push unsolicited messages at any moment. That convention is application design, not protocol, and it has to be versioned like any other contract.
code
json · 3 lines{"id": "c-4711", "kind": "command", "op": "hoist", "target": "bay-12"}
{"id": "c-4711", "kind": "reply", "status": "accepted"}
{"kind": "push", "event": "position", "bay": "bay-12", "height": 14.2}go deeper
Know that a WebSocket delivers messages, not calls. Nothing in a message says which earlier message it answers, so any request-and-reply behaviour is something the application adds on top.
Explain the machinery: a correlation field the requester generates and the responder echoes, a per-connection table of outstanding requests, a timeout on every entry, and defined handling for messages that match nothing.
Demonstrate the operational consequences - a reply arriving after its caller gave up, a capture you cannot untangle because the envelope has no correlation field, and a timeout that tells you nothing about whether the command ran.
Treat the envelope as a contract rather than a per-feature detail. One versioned message shape across the fleet is the difference between adding a request kind safely and discovering three incompatible conventions during an incident.
## A socket carries messages, not calls The mental model most candidates bring to a WebSocket is a call: you send something, you get something back. The protocol does not have that idea. What it has is two independent flows of whole messages over one connection, either end able to write whenever it likes. Nothing in a message identifies it, refers to another message, reports a status, or confirms that anything was received or processed. So in a crane cabin, the console sends `hoist bay-12` and a message comes back saying `accepted`. Nothing on the wire connects the two. If the console has three commands outstanding and two of them can be accepted, it cannot tell which reply is which. If the yard also pushes position updates and alarms on the same connection - which is the entire reason a socket was chosen - then the next message to arrive after a command very often is not its reply at all. ## What you have to invent Request-and-response over a socket is a convention the two ends agree and then both implement: 1. **An envelope with a correlation field.** The requester generates a value, puts it in the message, and the responder echoes it verbatim in the reply. It only has to be unique among that connection's outstanding requests, not globally. 2. **A table of outstanding requests.** Keyed by that value, holding whatever the caller is waiting on. A reply is matched by lookup; there is no other way to match it. 3. **A timeout on every entry.** This is the part that gets skipped and the part that matters most. Nothing will ever tell you a command was dropped, ignored, or answered by a peer that then vanished - so an entry with no expiry is a wait that can last forever. 4. **A rule for unmatched messages.** A message arriving with no entry is either a late reply to something you gave up on, or a message the peer sent on its own initiative. Both are normal, and a handler that treats an unmatched message as an error will fail on ordinary traffic. 5. **A way to distinguish a push from a reply.** Since both arrive the same way, the envelope needs a kind as well as a correlation value. ## The absences, stated precisely | What you might expect | What the protocol actually provides | |---|---| | A message identifier | none; any identifier is a field you added to the payload | | A reference from a reply to its request | none; the echo is your convention | | A status on a message | none; success and failure are values in your envelope | | A delivery or processing report | none; the sender learns nothing about a message's fate | | A timeout on a request | none; the connection has no idea a request is outstanding | What the protocol does give you is worth naming too, because it is easy to undervalue: message boundaries are preserved exactly, so a message is delivered whole or not at all, and you never have to find the edges of one yourself. ## Where the absence bites hardest - **Tests.** A test that sends a command and waits for *the next message* is asserting on arrival order rather than on an answer, and it will pass on a quiet connection and fail the moment the peer pushes anything else. The only correct wait is for a message whose correlation value matches what was sent. - **Logs and captures.** Without a correlation value in the envelope, a capture of a busy connection is an undifferentiated stream. You cannot reconstruct which command produced which outcome after the fact, and that is usually discovered during the incident rather than before it. - **Re-sending.** Because nothing reports delivery, a caller whose timeout fires genuinely does not know whether the command ran. Whether re-sending it is safe is a property of the command, and the socket will not help you decide. ## Design the envelope once The failure mode across a codebase is not that people fail to invent this - it is that each feature invents a slightly different version, so one message has an `id`, another a `requestId`, a third nothing at all. Define the envelope once for the connection, carry the correlation field on every message including pushes, and treat it as a versioned contract between the two ends.
- A test opens a socket, sends a command and waits for the reply. Why is 'the next message that arrives' the wrong thing to wait for?Because the peer may write at any moment on its own initiative, so the next message is quite often a push rather than an answer. The wait has to be for a message whose correlation value matches the one sent, with a timeout; anything else passes on an idle connection and fails under real traffic.
- What does a requester actually know when its timeout fires with no reply?Only that no matching reply arrived in time. The protocol reports nothing about delivery or processing, so the command may have been lost, may be queued, or may have completed with the reply lost on the way back. Whether re-sending is safe is a property of the command, not something the connection can tell you.
- Does a correlation value have to be globally unique?No. It only has to be unique among the requests outstanding on that connection, since the table it keys lives per connection, so a short counter is enough. Making it globally unique is useful for tracing across systems, which is a different requirement from matching a reply.
saying these in an interview costs you the question
- Assumes a reply is always the next message received
- Believes the protocol pairs a response with its request
- Leaves outstanding requests with no timeout
- Treats an unmatched message as a protocol error
- Thinks a sent message is confirmed as delivered