What span does http.Server.WriteTimeout cover, and why is it not a deadline on handler execution?
answer
- the clock does not start at the first Write
- reset whenever a request's headers are read
- a deadline on the socket, not on the work
- a slow downloader spends your budget
- the goroutine keeps running after it fires
basics
~20 shttp.Server.WriteTimeout starts when that request's headers have been read, so it covers the handler reading the body and the whole response reaching the client. It is a deadline on the connection: writes fail when it expires, but the handler goroutine keeps running.
solid answer
~50 s`WriteTimeout` is documented as the maximum duration before timing out writes of the response, and it is reset whenever a new request's headers are read — so the clock starts at the *end of the request headers*, not when the handler first calls `Write`. In practice it covers everything after the headers: the handler reading the request body, the handler doing its work, and the response bytes actually being accepted by the client. That last part is the surprise. A response that the server produced in a millisecond can still trip `WriteTimeout` if the client is on a slow link, because the deadline lives on the socket and slow draining counts against it. And it is not a handler deadline: when it expires the write returns an error and the connection is closed, but the goroutine running `ServeHTTP` is not stopped and keeps burning whatever it was burning. Bounding handler *work* needs a different tool.
code
go · 12 linesfunc streamHandler(w http.ResponseWriter, r *http.Request) {
rc := http.NewResponseController(w)
// The zero time removes the deadline for this response only.
if err := rc.SetWriteDeadline(time.Time{}); err != nil {
http.Error(w, "streaming unsupported", http.StatusInternalServerError)
return
}
for event := range events(r.Context()) {
fmt.Fprintf(w, "data: %s\n\n", event)
rc.Flush()
}
}go deeper
Know that WriteTimeout bounds the response side of a request and that a zero value means no bound at all; the exact moment the clock starts is what the next level asks about.
Explain that the deadline is reset when a request's headers are read, that it therefore spans body reads plus the response transfer, and that it is enforced on the socket.
Show the operational consequence: a slow client can trip it while the server was fast, and a handler that overruns it keeps holding its database rows, locks and memory.
Own the split between transport deadlines and work deadlines, and decide how streaming endpoints get their exemption without weakening the default for the rest of the fleet.
## Where the clock starts The documented behaviour of `http.Server.WriteTimeout` is that it is the maximum duration before timing out writes of the response, and that it **is reset whenever a new request's header is read**. That phrase is the whole answer to when the clock starts. It is not when the handler first writes, and it is not when the connection was accepted. It is the moment net/http finishes reading the request line and headers and is about to dispatch to your handler. So for one request on an HTTP/1.1 connection, the span covered is: 1. the handler reading the request body (if it reads one), 2. the handler doing its work, 3. the response headers and body being written to the socket and accepted by the client. ## Why that makes it a network deadline, not a work deadline The field is implemented by setting a write deadline on the underlying connection. A write deadline is a property of the socket: when it expires, subsequent writes fail immediately with a timeout error. Two consequences follow, and both are what interviewers are probing for. **It counts the client's download speed.** A 200 MB response that your service produced instantly will still blow a 30 second `WriteTimeout` if the client is on a link that cannot drain 200 MB in 30 seconds. The server was not slow; the transfer was. This is the classic reason a mobile client sees truncated large downloads that nobody can reproduce on the office network. **It does not stop your handler.** Nothing in Go can preempt and kill a goroutine from outside. When the deadline expires, the handler's next call to `ResponseWriter.Write` returns an error — an error most handlers ignore, because almost nobody checks the return value of `Write` on a `ResponseWriter`. A handler that is looping, waiting on a slow dependency or holding a lock will carry right on. If a handler never writes at all, `WriteTimeout` does nothing visible until the server flushes at the end of `ServeHTTP` and that flush fails. ## What the client sees Not a status code. By the time the deadline fires the response headers have usually already gone out, and there is no way to retract them. The connection is closed, and the client sees a truncated body or a reset. Any behaviour that must be expressed as an HTTP status has to be decided *above* the connection layer, inside or around the handler, where a status can still be chosen. ## Choosing a value The value has to cover the slowest legitimate combination of handler work and client download, which is why it is the field that generates exemption requests. Two shapes of endpoint break under any fleet-wide number: - **Streaming responses** — a server-sent-events stream, a log tail, a long poll. These are meant to stay open far longer than any sane default. - **Large downloads to slow clients** — where the byte count divided by the worst tolerable bandwidth exceeds the default. Since Go 1.20 the escape hatch for both is per request rather than per server: `http.NewResponseController(w)` returns a controller whose `SetWriteDeadline` adjusts or removes the deadline for this response only. Passing the zero `time.Time` clears the deadline, leaving that one handler unbounded while every other endpoint on the same server keeps the strict default. Before that API existed, teams routinely dropped `WriteTimeout` to zero server-wide for the sake of one streaming route, which is exactly the trade the per-request controller removes the need for. ## The separation worth stating out loud There are two different questions that sound the same: *how long may this connection take* and *how long may this handler run*. `ReadTimeout`, `WriteTimeout`, `ReadHeaderTimeout` and `IdleTimeout` answer the first. They are cheap, they protect the process against clients that misbehave at the transport level, and they cannot produce a nice error page. Answering the second — bounding the handler's own work, and returning a chosen status when it overruns — is a wrapper around the handler, and a handler that respects the cancellation of its request context. Candidates who conflate the two usually reach for `WriteTimeout` to fix a slow database query, and are surprised when the query keeps running and the connection pool keeps filling.
- A handler runs for two minutes without writing anything and WriteTimeout is 30 seconds. What happens?The handler runs the full two minutes. Nothing interrupts it. The write deadline has long expired, so the final flush when `ServeHTTP` returns fails and the connection is closed, but the work was done and any resources it held were held for the whole two minutes.
- How would you keep a strict WriteTimeout but still serve one long streaming endpoint?Relax it for that response only, from inside the handler, using `http.NewResponseController(w).SetWriteDeadline` with a later time or the zero time to clear it. The server-wide field stays strict for every other route, which is far better than dropping the field to zero for the whole process.
- Why do slow mobile clients see truncated large responses that nobody can reproduce internally?Because the write deadline covers the client accepting the bytes, not just the server producing them. On a fast internal network the transfer finishes well inside the budget; on a slow link the same response exceeds it and the connection is closed mid-body, with no status code to explain it.
saying these in an interview costs you the question
- Says the clock starts when the handler first calls Write
- Claims WriteTimeout aborts a slow handler goroutine
- Expects a 504 status when the write deadline expires
- Ignores that the client's download speed counts against it
- Drops the whole server's WriteTimeout to zero for one streaming route