skip to content

What does a client communicate by sending the HTTP request header Expect: 100-continue, and what is the server expected to do when it receives it?

level: middleimportance: must knowfreq 33%

answer

  1. Send headers, pause, wait for go-ahead
  2. 100 Continue = interim, body allowed
  3. Early 401/413 saves the upload
  4. Two responses for one request
  5. Only worth it for large bodies

basics

~20 s

The client sends headers only and waits before sending the body. The server either replies 100 Continue, telling it to send the body, or answers with a final status such as 401 or 413, letting the client skip the upload entirely.

solid answer

~50 s

`Expect: 100-continue` splits a request in two. The client writes the request line and headers, then pauses instead of streaming the body. The server inspects those headers — target, `Content-Length`, `Authorization`, `Content-Type` — and decides. If it wants the payload it sends the informational status `100 Continue`, an interim response with no body, and the client then writes the body; the server later sends the real final response on the same exchange. If it does not want the payload it can respond immediately with a final status — `401 Unauthorized`, `403`, `404`, `413 Content Too Large` — and the client abandons the upload. The point is saving a large, wasted transfer over a slow link. It is worth using for big bodies where rejection is plausible; it is pure overhead for small ones. A server that does not understand the expectation may simply say nothing and wait for the body, which is why clients need a fallback timeout.

code

http · 12 lines
http
PUT /uploads/large.bin HTTP/1.1
Host: files.example.com
Content-Length: 524288000
Content-Type: application/octet-stream
Expect: 100-continue

HTTP/1.1 100 Continue

[client now sends the 500 MB body]

HTTP/1.1 201 Created
Location: /uploads/large.bin

go deeper

for a junior

Say that the client sends headers first and waits for permission before uploading the body, so a rejection costs no bandwidth.

for a middle

Describe the three-leg exchange, name 100 as an interim status, and explain the size threshold that makes it worthwhile.

for a senior

Add server-side realities: early rejection and connection handling for unread body bytes, intermediaries, and why clients must tolerate a silent server.

for a principal

Position it against alternatives — stream cancellation in HTTP/2 or /3, presigned or pre-authorized upload endpoints, chunked or resumable protocols — and decide whether the round trip is worth it for your payload profile.

## The problem In plain HTTP a client sends the request line, the headers and the body in one continuous stream. If the server is going to reject the request — bad credentials, wrong content type, body too large, resource gone — it usually learns that from the headers alone, but the client has already committed to pushing the payload. On a 40 MB upload over a slow uplink that is minutes of transfer thrown away, plus bandwidth and server socket time. ## The mechanism `Expect` is a request header whose only standardized value is `100-continue`. Its meaning: *I am about to send a body, but I will wait for your go-ahead first.* The exchange has three legs instead of two: 1. Client sends request line + headers, including `Expect: 100-continue` and normally `Content-Length` (or `Transfer-Encoding: chunked`). Then it stops writing and reads. 2. Server evaluates the headers and answers one of: - **`100 Continue`** — an *informational* (1xx) interim response with no body. It is not the final answer; it only unblocks the client. - **A final response** — `401`, `403`, `404`, `405`, `413`, `415`, whatever the headers justify. The client should not send the body. - **`417 Expectation Failed`** — the expectation itself cannot be met. 3. If it received `100 Continue`, the client sends the body and then reads the real final response — `201`, `200`, `500`, and so on. So this request produced two responses on the wire: one interim, one final. Client libraries must be prepared for that. 1xx responses are also the reason a parser cannot assume the first status line is the answer. Any HTTP client that speaks this must loop over interim responses. ## Where it is used in practice - **Large uploads with plausible rejection:** object stores, media ingest, artifact registries. Rejecting an oversized or unauthenticated 5 GB PUT after 200 bytes rather than after 5 GB is a big saving. - **curl uses it automatically** for bodies above a size threshold, which is why engineers most often meet it as an unexplained pause rather than a deliberate design choice. - **Authenticated APIs** where a challenge is likely on the first attempt. It is a poor fit for small bodies: the extra round trip costs more than the payload it might save, which is exactly why HTTP client libraries either disable it by default or apply a size threshold. ## What the server may not do A server that sends `100 Continue` has not committed to success; it may still fail the request afterwards. Conversely, a server may skip the interim response and just read the body — that is legal, and the client's timeout fallback covers it. A server that answers with a final status before the body must be careful about what happens to the connection: if the client has already begun writing, or will write, the unread bytes sit in the socket. Many servers therefore close the connection after an early rejection, or drain a bounded number of bytes first, because leaving unread body data in the buffer corrupts the framing of the next request on a persistent connection. ## Version notes The mechanism is defined for HTTP/1.1 (RFC 9110 §10.1.1). It carries over to HTTP/2 and HTTP/3, where interim responses are ordinary HEADERS frames on the stream, and where the practical benefit is smaller because streams are cheap and can be reset with `RST_STREAM` — a server can cancel a stream mid-upload instead of relying on the client to have paused. The header is hop-by-hop-ish in behaviour: an intermediary that forwards the request is responsible for handling the expectation toward the origin. ## Signals to remember `100` is not a success code you handle in application logic; it is plumbing. `Expect` is one of very few headers whose absence changes nothing but whose presence changes the shape of the exchange itself.

  • Does receiving 100 Continue guarantee the request will succeed?
    No. It is an interim response that only says the server is willing to read the body; the final status arrives afterwards and can still be 4xx or 5xx. Validation that depends on the body — checksums, schema, virus scanning, quota after the fact — can only be applied once the payload has been received.
  • Why is it not worth using for a small JSON POST?
    It adds a full round trip before any payload moves, which for a one-kilobyte body costs far more time than the transfer it might avoid. The mechanism only pays off when the body is large or the link is slow enough that the potential wasted transfer exceeds one round-trip time.

Phoning ahead before driving a truckload across town to ask whether the loading dock will accept it.

saying these in an interview costs you the question

  • Treating 100 Continue as a success status handled by application code
  • Assuming the server must send 100 Continue rather than being allowed to just read the body
  • Believing 100 Continue commits the server to a 2xx final response
  • Enabling the expectation on every request regardless of body size
  • Writing a client that reads only one status line per request

context