In a WebSocket connection, what happens on the wire between one endpoint deciding to close and the TCP connection actually ending?
answer
- it is a handshake, not a hang-up
- the peer answers with its own
- two bytes of status, then UTF-8 reason
- no data frames after yours goes out
- the server closes the transport last
basics
~20 sThe closing endpoint sends a Close frame and stops sending data; the peer sends a Close frame back. Only once both directions have exchanged one does the transport go: the server closes the TCP connection and the client waits for that close.
solid answer
~50 sClosing a WebSocket is a handshake, not a disconnect. One endpoint sends a Close control frame (`%x8`), and from that moment it must not send any more data frames. An endpoint that receives a Close it did not initiate must send a Close frame back — it may finish the message it is in the middle of first, and it typically echoes the status code it received. Once an endpoint has both sent and received a Close, the connection is considered closed and the transport is torn down: the server closes the TCP connection immediately and the client waits for that rather than closing first. The echo is the point. A bare transport-level teardown cannot distinguish a deliberate shutdown from a cut cable, whereas a Close frame carries an agreed status code and an optional reason both ends can log.
code
pseudocode · 12 lines// endpoint A initiates
payload = uint16_network_order(1001) + utf8("node draining")
send control frame CLOSE with payload
sending_data_frames = false // no data frame may follow
// endpoint B, which did not initiate
on receive control frame CLOSE(status, reason):
finish sending the message already in flight
send control frame CLOSE with payload = uint16_network_order(status)
// both sides have now sent and received a CLOSE:
// the server closes the TCP connection immediately,
// the client waits for that close rather than racing itgo deeper
Remember that closing is an exchange of Close frames, not just dropping the connection, and that 1000 means a normal ending while 1001 means this endpoint is going away.
Explain the body's layout — two bytes of unsigned status in network byte order, then optional UTF-8 reason — and the rule that no data frames may follow your own Close frame.
Show why the echo exists: it separates a deliberate shutdown from a loss in your operational data, and it gives both sides the same recorded status and reason to page on.
The judgement is what your services agree a status code means across teams — a shared convention for draining, overload and fault turns close codes into a fleet-wide signal rather than per-service noise.
## Why closing needs a handshake A WebSocket lives on one TCP connection for its whole life, and TCP already has a way to end a connection: a FIN in each direction. So why does `RFC 6455` define a closing handshake on top of it? Because a transport-level teardown carries no meaning. A FIN that arrives after a network partition, after a process was killed, and after a deliberate shutdown all look identical to the receiver. Worse, a connection cut by an intermediary may produce no teardown at all on your side. The Close frame fixes both problems: it is an in-band statement, from the application layer, that *this* endpoint is ending the connection, and here is why. ## The Close frame and its body Close is a control frame, `opcode %x8`, and like every control frame it is capped at 125 bytes of payload and may not be fragmented. Its body is **optional**, and when present it is laid out in exactly two parts: 1. The first two bytes are a **status code**: a 2-byte unsigned integer in network byte order. 2. Anything after those two bytes is an optional **reason**, encoded as UTF-8 text. It is for humans and logs; nothing in the protocol branches on it. A Close frame with no body at all is legal and means no status was supplied. You cannot supply a reason without a status, because the reason's position is defined only relative to the two status bytes. ## The sequence, step by step 1. Endpoint A decides to close. It sends a Close frame, with a status code if it has something to say. 2. A **must not** send any further data frames. It may still read what the peer has already put on the wire. 3. Endpoint B receives the Close. If it has not already sent one of its own, it **must** send a Close frame in response. It may delay that until it has finished sending the message it was in the middle of, and it typically echoes back the status code it received. 4. Each endpoint, having now both sent and received a Close, considers the WebSocket connection closed. 5. The transport goes last: the server closes the underlying TCP connection immediately, and the client waits for that close rather than racing it — though a client that waits a reasonable time and sees nothing may close anyway. That last step is the one candidates reverse. "Whoever started it hangs up" is intuitive and is not what the specification says. ## The codes you will actually send | Code | Name | When an endpoint sends it | |---|---|---| | `1000` | normal closure | The connection did its job and is finished — the ordinary case. | | `1001` | going away | This endpoint is leaving: a node draining before a deploy, or a peer navigating away from the surface that opened the socket. | | `1002` | protocol error | The peer sent something that violates the protocol, so this endpoint is terminating the connection. | | `1011` | unexpected condition | The server hit an unexpected condition that stopped it fulfilling the request — its own fault, not the peer's. | The 1000-range holds further codes for specific faults, each tied to the mechanism it describes. The four above are the ones a general-purpose endpoint chooses between. ## What a clean close buys you - **An agreed ending.** Both sides recorded the same status, so two logs from two operators tell the same story. - **No ambiguity with failure.** An operations view can separate deliberate shutdowns from losses, instead of counting both as churn. - **A reason string.** `1001` plus "node draining" is a different on-call page from `1001` alone. - **A defined quiet point.** After the exchange, neither side is still trying to send data frames into a connection that is going away. Everything in that list is lost when the connection ends without a Close frame — and the cost of that loss is why the difference between a clean and an unclean ending is worth an interview question of its own.
- May a WebSocket endpoint keep sending data frames after it has sent a Close frame?No. Once it has sent a Close frame it must not send any more data frames. It may still read frames the peer has already put on the wire, and it must still handle the peer's Close frame when it arrives. The one permitted delay is on the other side: an endpoint that received a Close may finish the message it was mid-way through before echoing.
- What does a WebSocket Close frame with an empty body mean?That no status code was supplied — which is legal. The body is optional, and a reason cannot be sent without a status because the reason is defined by its position after the two status bytes. A receiving application usually reports the absence with a reserved local code rather than inventing `1000`.
- When is `1001` the right status rather than `1000`?`1000` says the connection's purpose was fulfilled and it is ending normally. `1001` says this endpoint is going away while the connection was otherwise fine — a node draining before a deploy, or a peer leaving the surface that opened the socket. The distinction matters to whoever reads the close reason later: one is completion, the other is departure.
A sealed door that can only be confirmed shut from the other side: you say you are leaving and wait to hear it acknowledged, because a door that merely stops rattling might have been shut — or might have been blown off its hinges.
saying these in an interview costs you the question
- Says the endpoint that started the close tears down the TCP connection.
- Thinks closing means simply dropping the transport connection.
- Believes the Close frame's body is required.
- Puts the reason text before the status code in the body.
- Keeps sending data frames after its own Close frame went out.
- Uses 1011 for a planned shutdown rather than a server fault.