skip to content

A streaming endpoint fails halfway through a body whose 200 status was already sent — how should the server end that response?

level: seniorimportance: should knowfreq 48%

answer

  1. only the ending is still yours
  2. clean finish equals silent success
  3. truncation is the detectable signal
  4. every prefix of a record stream is valid
  5. require an end-of-stream marker

basics

~20 s

Do not end it cleanly. Abort so the body stops short of its declared length or terminator — the one incompleteness a receiver can detect — and record the failure server-side. A trailer or agreed end marker is extra.

solid answer

~50 s

The decision is **abort versus finish**, and finishing is almost always wrong. A body that terminates normally is indistinguishable from a complete one, so a consumer that was streaming records will happily commit a half result. Aborting leaves the message short of what its framing promised, and that is the only in-band signal a generic client is guaranteed to notice. Two things make the abort more useful: a **trailer field** carrying a machine-readable reason, when the response was framed to allow trailers and the consumer actually reads them; and, for record-oriented streams, an **explicit end-of-stream marker** the consumer requires before treating a result as whole — its absence turns a truncation into a detectable error even on a media type happy to end anywhere. Whatever the wire signal, the failure must also be recorded server-side, because the frozen `200` will otherwise be the only thing the logs remember.

go deeper

for a junior

Know that a response that stops early is not automatically an error to the client. If the message ends the way its framing said it would, the client treats a half body as the whole answer.

for a middle

Explain the abort-versus-finish choice and why truncation is detectable while a clean ending is not. Mention that a trailer can carry a reason but is often dropped before the client sees it.

for a senior

Bring the consumer contract into it: an end-of-stream marker or count that makes completeness positive, plus the server-side record and a metric of its own, because the request's logged status is still the committed one.

for a principal

Decide which endpoints are allowed to stream at all, and make the completeness contract part of the API standard rather than something each endpoint improvises when its first incident arrives.

## Why a clean ending is the dangerous choice When a handler is streaming, the status line has long since gone out. The one decision left is how the message stops. A framework can bring the body to a normal end — completing the framing the response promised — or it can tear the connection down so the body is short. A normal end is a **lie with no tell**. The receiver sees a well-formed message with the status it was promised, so a client library reports success, a cache may store the truncated body, and a downstream consumer commits a partial result. Because nothing on the wire distinguishes it from a complete response, the failure surfaces days later as missing rows rather than as an error at the moment it happened. Aborting is the opposite trade: the receiver gets less information about *why*, but it reliably learns *that* something went wrong, because the body stopped before the framing said it would. ## The signals available once the status is frozen | Signal | What the consumer must already do | Reliability | |---|---|---| | Truncation via abort | Nothing — generic clients surface it as a transport or parse error | High; this is the default detection path | | Trailer field with a reason | Read trailers, and the response must be framed to carry them | Low to moderate; intermediaries and many clients discard them | | In-band error record or sentinel | Parse the payload and honour an agreed marker | High, but only for consumers built to your contract | | Required end-of-stream marker | Refuse to treat a stream as complete without it | High for your own clients; needs designing up front | | Server-side record only | Nothing | Zero client-side value, but always available | The practical answer for a public endpoint is truncation plus a server-side record. The practical answer for an endpoint whose consumers you also own is truncation plus a contract that makes truncation detectable at the application layer. ## Self-delimiting payloads versus record streams The shape of the payload decides how loud the truncation is. - A **single structured document** — one object or array that must be closed — is largely self-protecting. Cut it anywhere and the parser hits the end of input with the structure still open, so most consumers fail immediately even without an explicit marker. - A **record-per-line or record-per-event stream** is the hazard. Every prefix of it is itself a valid stream of records. A consumer reading until the stream ends cannot tell a stream that ended because there were no more rows from one that ended because the producer died on row 40,000. - **Length-framed content** sits in between: the declared size makes a short body detectable at the transport layer, provided the length was known and declared before commit. For the middle case, design the contract so completeness is positive rather than assumed: a final marker record, a count the consumer compares, or a checksum. The consumer's rule becomes *no marker, no commit* — and then a mid-stream abort is an error the consumer raises itself. ## What the server must do regardless of the wire signal 1. **Stop producing immediately.** Continuing to write after the failure risks emitting records the upstream work never finished, which is worse than a short stream. 2. **Record the failure with the request's correlation identifier**, and make the record say explicitly that it happened after commit — that distinction is what tells a reader why no error status was ever returned. 3. **Count it on its own metric.** The recorded status for the request is the committed one, so nothing built on response status will notice this class of failure. 4. **Release the work behind the stream.** A cursor, a file handle, or an upstream call is often still open; the abort path is the one most likely to skip cleanup, and it is the path that runs during an incident. 5. **Count a failure your own code caused separately from a stream the peer stopped reading.** They need different responses and should not share an alert. ## Designing so the choice arises less often Much of what fails mid-stream does not have to be mid-stream. Resolving permissions, opening the upstream source, and validating parameters can all happen before the first byte, which converts a large class of would-be truncations into ordinary mapped error responses. What genuinely remains — the upstream dying at row 40,000 — is the irreducible part, and that is what the end-of-stream contract exists for. The judgment an interviewer listens for is this: you are not choosing between a good option and a bad one after commit. You are choosing the **least dishonest** ending, and you are designing the consumer contract so that the least dishonest ending is also a detectable one.

  • Why is a newline-delimited record stream more dangerous to truncate than a single structured document?
    Because every prefix of a record stream is itself a well-formed stream, so a consumer cannot tell a short result from a complete one. A single document that must be closed fails to parse when cut, which gives the consumer an error for free.
  • When is a trailer field a reasonable place to report a mid-stream failure?
    When the response framing carries trailers and you also own the consumer, so you know it reads them. It is an enrichment, never the primary signal: intermediaries and general-purpose clients commonly drop trailers, and a consumer that ignores them sees a normally ended body.
  • What should the server record when it aborts a committed response?
    The failure itself with the request's correlation identifier, an explicit marker that it happened after commit, how many bytes or records were emitted, and a counter separate from ordinary handler errors — since the request's recorded status is still the committed success.

saying these in an interview costs you the question

  • Ends the message normally on failure so the response stays well-formed
  • Assumes the client will notice a short body without any framing that declares length
  • Relies on a trailer as the only failure signal for arbitrary clients
  • Keeps writing records after the upstream producing them has failed
  • Thinks a retry is safe because the status the client received was 200
  • Forgets to release the cursor or upstream call on the abort path