In a web framework, what becomes fixed the moment a streaming handler's first bytes are flushed to the client?
answer
- headers travel ahead of the body
- there is only one status line
- commit happens at first flush
- framing is chosen before the first byte
basics
~20 sHeaders travel ahead of the body, so the first flush freezes the status code, every header field, cookies and the body's framing choice. Later changes to them are lost; only more body bytes can follow.
solid answer
~40 sA message is written status line, then headers, then body, so flushing the first body bytes means the envelope is already on the wire. From that point the status code, every header field, any cookie and the framing decision are fixed — frameworks variously ignore a late header write, log it, or raise at the call site, so assume it is simply lost. The framing part matters: if a length was set before the first write the response is committed to exactly that many bytes, and if it was not, the framework uses a length-free framing that must be terminated cleanly by closing the sink. The practical rule is to set the status and every header, and do everything that can still fail cheaply, before the first write.
go deeper
Remember the order on the wire: status line, then headers, then body. Once body bytes have been flushed, the parts in front of them are already sent and cannot be edited.
Explain commit precisely — which fields freeze, that the framing choice freezes with them, and that a late header write is variously ignored, logged or rejected depending on the implementation, so never rely on it.
Demonstrate the habit: validate, authorise and fetch the first record before the first write, so anything that can fail still becomes a proper status. Diagnose 'my 500 arrived as a 200' as a commit-ordering symptom.
Treat the commit point as an API design constraint. Where a body can fail halfway, the contract has to define what a consumer treats as complete, and that decision belongs in the interface, not in each handler's improvisation.
A response goes out in a fixed order: a status line, then the header fields, then the body. That order is not a framework convention, it is how the message is laid out on the connection, and it has a consequence every streaming handler runs into. The instant the framework flushes the first body bytes, the status and headers have **already** been written ahead of them, and nothing can call them back. ## What "committed" means **Committing** a response is the moment the framework stops holding the response in a mutable in-memory form and starts putting it on the wire. Before that moment the status code, every header field and the body are just values the framework owns and can still change. After it, the client — and every intermediary in between — may already have parsed the status line and the headers and acted on them. For a buffered handler the commit happens after the handler returns, which is why buffered code can change its mind freely. For a streaming handler the commit happens at the **first flush**, usually the first write large enough to fill the output buffer or the first explicit flush, so the commit lands in the middle of the handler's own execution. ## What is fixed at that moment - **The status code.** Whatever the handler set before the first write is the status the client sees. There is no second status line. - **Every header field**, including content type, caching directives, and any header that carries a redirect or authentication challenge. - **Cookies**, because they are set through header fields and travel with them. - **The body's framing decision.** If a length was set before the first write, the connection is committed to delivering exactly that many bytes; if not, the framework falls back to a framing that terminates without a declared length, typically `Transfer-Encoding: chunked` on HTTP/1.1 or the protocol's equivalent on later versions. - **The bytes already written.** They are gone. A body can be added to, never edited or withdrawn. | After the first flush | Still possible? | |---|---| | Change the status code | No, the status line is already on the wire | | Add or edit a header field or cookie | No, the header block has been sent | | Switch the body's framing | No, the framing was announced with the headers | | Withdraw or edit bytes already written | No, a body can only be added to | | Keep writing more of the body | Yes, until the sink is closed | What is *not* fixed is the rest of the body and, on most implementations, trailing metadata that the framing allows after the body. Frameworks differ in how they react to a late header write: some silently ignore it, some log a warning, some raise an error at the call site. Relying on any one of those behaviours is a portability bug; the safe assumption is that the write is simply lost. ## Why the framing decision is part of the commitment Before the first byte the framework has a choice. It can ask the handler for a length, or it can announce a length-free framing. Once the headers are on the wire that choice is locked, and both directions are unforgiving: 1. If a length was declared and the handler writes fewer bytes, the client waits for bytes that never come, until a timeout or a connection close makes the message look truncated. 2. If a length was declared and the handler writes more, the extra bytes are not part of the message; on a reused connection they can be misread as the start of the next one. 3. If no length was declared, the handler must close the sink properly so the framing can signal a clean end — otherwise a truncated stream and a complete one look identical to the consumer. ## A practical checklist for a streaming handler The rule that follows is blunt and worth internalising: **decide everything about the response envelope before you write the first byte.** - Set the status and all headers, including the content type and any caching or content-disposition fields, before the first write. - Do the work that can still fail cheaply — authorisation, validating parameters, opening the source, fetching the first record — *before* the first write, so a failure can still become a proper error status. - Only then start producing the body, and close the sink explicitly when done. The reason for the second bullet is the most valuable habit here. Everything a handler can check up front is a failure it can still report properly. Everything it defers past the first flush becomes a failure it can only signal inside the body or by breaking the connection — which is a different problem with its own rules. ## Why interviewers ask this It explains a whole family of confusing bug reports: a handler that "sets a 500 but the client sees 200", a redirect that has no effect, a cookie that never appears, a content type that stays at the default. In every case the code ran, the write happened, and it happened after the response was already committed. Once a candidate can say *headers go first, the first flush commits them, and later changes are lost*, all of those symptoms collapse into one explanation.
- Why can the framework not simply buffer the headers until the handler finishes?Because then it would not be streaming. Holding the headers means holding the body behind them, which reinstates the full in-memory copy and the delayed first byte that streaming exists to avoid. Committing early is the price of sending early.
- What decides whether the response goes out with a declared length or a length-free framing?Whether a length is known before the first flush. If the handler or framework set one, that count is binding and the body must match it exactly. If not, the framework announces a framing that terminates without a declared length and signals the end when the sink is closed.
- A handler sets a header right after its first write and the client never sees it. Is that a framework bug?No, it is the commit. That field was written after the header block had already been sent. Frameworks differ in whether they ignore the late write, log it, or raise — none of them can retract bytes the client may already have parsed.
saying these in an interview costs you the question
- Believes headers can still be changed while the body is streaming
- Thinks a second status code can be sent later in the response
- Assumes the framework buffers headers until the handler returns
- Sets the content type after writing the first rows
- Thinks a declared length is advisory and extra bytes are harmless
- Expects a late cookie or redirect header to reach the client