Go

Why must you close resp.Body, and why is closing alone sometimes not enough?

Question 448MediumGo 1.22 to 1.25

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

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