Go

How does net/http's server handle concurrency internally? What are the consequences for handler code?

Question 443MediumGo 1.22 to 1.25

http.Server.Serve runs an accept loop; for every accepted net.Conn it starts one goroutine (go c.serve(ctx)). With HTTP/1.1 keep-alive, that goroutine reads requests on the connection sequentially and calls your handler for each. With HTTP/2 each stream gets its own handler goroutine, so many requests on one connection run concurrently.

Consequences:

  • Handlers run concurrently: any shared state (maps, counters, caches) must be protected with a mutex, sync/atomic or channels.
  • A panic in a handler is recovered by the server (it logs and closes the connection) — but a panic in a goroutine you spawn from the handler crashes the whole process.
  • There is no built-in concurrency limit; under load you can get unbounded goroutines. Use a semaphore middleware, load shedding, or netutil.LimitListener.
  • Goroutines you start must not use the ResponseWriter after the handler returns.
var hits atomic.Int64

func handler(w http.ResponseWriter, r *http.Request) {
	n := hits.Add(1) // safe: handlers run concurrently
	fmt.Fprintf(w, "hit %d\n", n)
}

Interviewer looks for: "goroutine per connection", awareness that handlers are concurrent, and the panic-in-spawned-goroutine gotcha.

More on Standard Library, HTTP & Systems Design in Go

All 35 Standard Library, HTTP & Systems Design in Go questions