In Go, resource management is critical because unlike memory (which is garbage collected), external resources like file descriptors and network sockets must be released explicitly. Failure to do so leads to leaks that can crash your application.
Here is a checklist of the most common things you need to close, typically using the defer keyword.
This is the most common source of leaks in Go. When you make an HTTP request using the net/http client, you must close the response body, even if you don't read it. If you don't, the underlying network connection cannot be reused (keep-alive) or released.
- What to close:
http.Response.Body - Pattern:
resp, err := http.Get("https://api.example.com")
if err != nil {
return err
}
// Crucial: Close the body when the function returns
defer resp.Body.Close()
// Read the body...When you open or create a file, the operating system allocates a file descriptor. If you run out of file descriptors, your program will panic or fail to open new files/sockets.
- What to close:
*os.Filereturned byos.Open,os.Create, etc. - Pattern:
f, err := os.Open("data.txt")
if err != nil {
return err
}
defer f.Close()While the main sql.DB connection pool is usually long-lived (and closed only on app shutdown), individual query results must be closed. If you fail to close rows, the connection remains "busy" and doesn't return to the pool.
- What to close:
*sql.Rowsreturned bydb.Query(not needed forQueryRoworExec). - Pattern:
rows, err := db.Query("SELECT * FROM users")
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
// scan...
}
// Always check rows.Err() after the loopWrappers like gzip.Writer, zip.Writer, or tar.Writer buffer data. Closing them is required not just to release resources, but to flush the remaining data and write the file footer (checksums, size). If you don't close them, the output file will be corrupt/incomplete.
- What to close:
gzip.Writer,zlib.Writer, etc. - Pattern:
// Example: compressing data to a buffer
var buf bytes.Buffer
zw := gzip.NewWriter(&buf)
_, err := zw.Write([]byte("data"))
// You MUST close zw before reading buf, or the data will be incomplete
zw.Close() If you are working with raw TCP/UDP sockets using the net package, you need to close connections and listeners when you are done.
- What to close:
net.Conn,net.Listener - Pattern:
conn, err := net.Dial("tcp", "golang.org:80")
if err != nil {
return err
}
defer conn.Close()A common mistake is to defer the close immediately, before checking if the error is nil. If os.Open fails, f will be nil, and calling f.Close() will cause a panic.
Bad:
f, err := os.Open("file.txt")
defer f.Close() // PANIC if err != nil
if err != nil { ... }Good:
f, err := os.Open("file.txt")
if err != nil {
return err
}
defer f.Close()defer ignores return values. For reading files, this is usually fine. However, for writing files (especially over networks or zip archives), the Close() method might return an error indicating that the flush to disk failed.
If data integrity is critical, handle the close explicitly at the end:
f, err := os.Create("important_data.txt")
if err != nil { return err }
defer f.Close() // Safety net
// ... write data ...
// Explicit close to check for write errors
err = f.Close()
if err != nil {
return fmt.Errorf("failed to close file: %w", err)
}