skip to content

A handler fails halfway through writing a large response and the client receives a success status with a truncated body instead of your error page. Why, and how do you fix it?

level: seniorimportance: must knowfreq 58%

answer

  1. the status was already correct when sent
  2. buffer overflow committed it early
  3. error stage has nothing left to set
  4. truncation is the only signal remaining
  5. logs count these as successes

basics

~20 s

The response committed before the failure, so the success status and headers were already on the wire and the error stage had nothing left to set. Do fallible work before the first byte, and give streamed responses a completeness signal.

solid answer

~40 s

Writing a large body overflowed the write buffer, which committed the response. From that moment the status line and headers were gone, so when the handler threw, the error stage could not replace a `200` with a `500` - it could only stop writing or drop the connection. The client sees a success status and a body that ends mid-structure. The repairs are layered: move every fallible step - authorization, queries, conversion, validation - ahead of the first write; keep ordinary responses inside the buffer so commit happens at the end; for genuinely streamed responses, define an end marker or an envelope the client checks, and abort the connection on failure so a careful client notices. Then add a metric for post-commit failures, because status-code dashboards will report these as successes.

go deeper

for a junior

Recall the rule behind the symptom: once the first bytes are sent the status is fixed, so an error later in the handler cannot change it. Do risky work before writing output.

for a middle

Explain the mechanism end to end - buffer overflow commits, commit freezes the status line, the error stage can only truncate - and say how the size of the payload decides whether it happens.

for a senior

Deliver both halves: prevent early commit for normal responses, and make truncation detectable for real streams. Add the monitoring point, since these failures are logged as successes.

for a principal

Own the systemic version: a platform default for buffer sizing, a sanctioned completeness convention for streamed endpoints, and an error stage that behaves sanely on a committed response, so each team does not rediscover this.

## What actually happened The handler produced more body bytes than the write buffer held. The framework had to drain the buffer to keep going, and that drain **committed** the response: the status line and the whole header block went to the client. Everything after that point is append-only. When the failure occurred, the framework did what it always does - unwound to the error stage, which tries to build a response: choose a status, set a content type, render a message. All three are operations on a committed response, so they either raise, warn or vanish silently. The only real capability left is to stop writing, and optionally abort the connection. So the client gets: - the success status that was correct at the time it was sent; - the headers chosen for the happy path; - a body that stops mid-structure - an unclosed document, a half row, a partial record. ## Why this is worse than a plain error | Concern | Failure before commit | Failure after commit | |---|---|---| | Status the client sees | The error status you chose | A success status | | Body | Your error representation | Truncated content | | Retry decision by the client | Informed by the status | Requires parsing or length checks | | Access log and status dashboards | Counted as an error | Counted as a success | | Caching intermediaries | Handle an error normally | May treat the partial result as fine | The monitoring row is what turns this into an outage that nobody notices. Alerting on error rates stays flat while clients receive garbage. ## Diagnosing it 1. **Confirm the shape.** Truncated bodies with success statuses, concentrated on the largest payloads or slowest queries, is the signature. 2. **Compare sizes.** If the response declared a length, clients get a short read; if it did not, the stream simply ended without its terminator. Both are detectable at the client and in a proxy log that records bytes sent. 3. **Look for the first write.** Find where body output begins in the handler and list everything fallible that happens after it. That list is the bug. 4. **Check the buffer boundary.** If the failures start above a particular payload size, you have found the commit trigger. ## Fixing it, in order of leverage - **Move fallible work before the first byte.** Authorization checks, data access, conversion and validation should all complete while the response is still uncommitted. Produce the body only from material that can no longer fail. - **Let ordinary responses commit at the end.** If a response comfortably fits the buffer, it never enters this failure mode, because the error stage always finds an uncommitted response. Sizing the buffer above the normal payload is a cheap, blunt, effective fix. - **Make streaming explicit and detectable.** When a response genuinely must be produced incrementally, the client needs a way to tell a complete response from a truncated one: a terminator record, a trailing status field in an envelope, a declared item count, or the absence of the stream terminator plus a deliberate connection abort on failure. Ending the connection abruptly is the strongest signal the protocol still gives you after commit. - **Teach shared error handling about commit.** A generic error stage should ask whether the response is already committed and, if it is, log and abort rather than attempting a write that will throw a second, confusing error. - **Instrument it separately.** Add a counter for failures that occur after commit, and alert on it. Also track short reads or parse failures on the client side, since that is where the damage lands. ## The same trap with a redirect A redirect helper sets a 3xx status and a `Location` header and, in most frameworks, writes the response out immediately. Code that keeps running after it - a template render, a late cookie, another redirect - is operating on a committed response and produces the same class of failure: a warning, a raised error, or a confusing body appended under a redirect status. Treat a redirect as terminal and return from the handler at once. ## What interviewers listen for A weak answer blames the client or the serializer. A strong answer names the commit point, explains why the error stage is powerless afterwards, and separates the two fixes: **avoid committing early** for ordinary responses, and **make incompleteness detectable** for responses that must stream. Mentioning that dashboards lie in this scenario is usually what marks real experience.

  • How can a client detect a truncated response at all?
    If a length was declared, the client sees fewer bytes than promised and the read ends short. If the response was delimited instead, the stream ends without its terminator and the connection drops. Above that, an application-level envelope, terminator record or expected count makes completeness checkable rather than inferred.
  • Why not simply buffer every response completely so this can never happen?
    Because buffering holds the whole body in memory per in-flight request and delays the first byte until the last one is produced. For large exports or long-running output that is unaffordable. Buffer what fits comfortably, and stream deliberately where it does not.
  • What should a shared error stage do when it finds the response already committed?
    Stop trying to build a response. Log the failure with enough context to identify the request, increment a post-commit failure metric, and abort the connection so a careful client sees an incomplete transfer instead of a clean end. Writing an error body into a success response only corrupts the payload.

saying these in an interview costs you the question

  • Blames the client for not handling the error status correctly
  • Suggests setting the status to 500 inside the error handler anyway
  • Thinks appending an error message to the partial body fixes it
  • Believes status-code dashboards would have caught this
  • Proposes catching the exception without moving work before the first write
  • Assumes the framework can retract bytes it has already sent