Why must you close resp.Body, and why is closing alone sometimes not enough?
The body is a stream over the underlying connection. Until it is closed, the Transport cannot reuse or free the connection — forgetting Close leaks a goroutine and a file descriptor per request. You must close it whenever err == nil, even if you don't read it and even for non-2xx statuses.
For HTTP/1.1 the connection is returned to the pool only if the body was read to EOF before closing. If you close early with unread data, the Transport closes the TCP connection instead of reusing it. So when you intentionally ignore the body, drain it (bounded):
resp, err := client.Do(req)
if err != nil {
return err // resp is nil (except rare redirect cases); nothing to close
}
defer func() {
io.Copy(io.Discard, io.LimitReader(resp.Body, 64<<10))
resp.Body.Close()
}()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected status %s", resp.Status)
}
return json.NewDecoder(resp.Body).Decode(&out)
Gotchas: defer resp.Body.Close() must come after the error check (otherwise nil dereference). Inside a loop, defer accumulates until the function returns — close explicitly or extract a helper. bodyclose linter catches these.
More on Standard Library, HTTP & Systems Design in Go
- Q446Why is http.ListenAndServe(":8080", nil) not production-ready? Explain each server timeout.
- Q447How does http.Client connection pooling work, and what are common mistakes with http.Client?
- Q449How is context used in net/http on both server and client sides? What happens when a client disconnects mid-request?
- Q450What does this handler send to the client, and why?
- Q451Explain encoding/json struct tag behavior: omitempty, omitzero, "-", and ",string". What does this print?
- Q452What does this print? How do you preserve large integer precision when decoding unknown JSON?