> ## Content Index
> Fetch the complete content index at: https://huizhou92.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Common Causes of Memory Leaks in Go: How to Avoid Them
- URL: https://huizhou92.com/common-causes-of-memory-leaks-in-go-how-to-avoid-them/
- Published: 2024-09-25T10:24:49.000Z
- Updated: 2026-09-08T02:30:24.000Z
- Description: Avoid memory leaks in Go by closing files and HTTP responses, managing goroutines properly, and using `defer` wisely. #GoLang #MemoryManagement
- Author: huizhou92
- Tags: tech, go, #lang-en

Memory leaks are a common issue regardless of the programming language used. However, it's relatively complex to write memory-leaking code in Go. This article will illustrate several scenarios where memory leaks may occur, allowing us to learn how to avoid them by studying these anti-patterns.

> This article was first published in the Medium MPP plan. If you're a Medium user, please follow me on [Medium](https://medium.huizhou92.com/?ref=huizhou92.com). Thank you!

## Resource Leaks

### Not Closing Opened Files

When you finish an open file, you should always call its `Close` method. Failing to do so may result in reaching the maximum number of file descriptors, preventing you from opening new files or connections. This can lead to a "too many open files" error, as shown below:

**Code Example 1**: Not closing files leads to file descriptor exhaustion.

```go
func main() {  
    files := make([]*os.File, 0)  
    for i := 0; ; i++ {  
       file, err := os.OpenFile("test.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)  
       if err != nil {  
          fmt.Printf("Error at file %d: %v\n", i, err)  
          break  
       } else {  
          _, _ = file.Write([]byte("Hello, World!"))  
          files = append(files, file)  
       }  
    }  
}

```

**Error Output**:

```
Error at file 61437: open test.log: too many open files

```

On my Mac, the maximum number of file handles a process can open is 61,440\. Go processes typically open three file descriptors (`stderr`, `stdout`, `stdin`), leaving a maximum of 61,437 open files. You can [manually adjust this limit](https://huizhou92.com/content/files/2026/09/mac-set-maximum-connection-and-port-parameters-1a3b114aad49.html).

### Not Closing `http.Response.Body`

Go has a well-known bug where forgetting to close the body of an HTTP request can lead to memory leaks. For example:

**Code Example 2**: Not closing the HTTP body causes a memory leak.

```go
func makeRequest() {  
    client := &http.Client{}  
    req, err := http.NewRequest(http.MethodGet, "http://localhost:8081", nil)  
    res, err := client.Do(req)  
    if err != nil {  
       fmt.Println(err)  
    }  
    _, err = ioutil.ReadAll(res.Body)  
    // defer res.Body.Close()  
    if err != nil {  
       fmt.Println(err)  
    }  
}

```

For more details on this issue, you can refer to:

- [Is resp.Body.Close() necessary if we don't read anything from the body?](https://stackoverflow.com/questions/18598780/is-resp-body-close-necessary-if-we-dont-read-anything-from-the-body?ref=huizhou92.com)
- [Must Close GoLang HTTP Response](https://manishrjain.com/must-close-golang-http-response?ref=huizhou92.com)

## String and Slice Memory Leaks

The Go spec does not explicitly state whether substrings share memory with their original string. However, the compiler allows this behavior, which is generally good as it reduces memory and CPU usage. But this can sometimes lead to **temporary memory leaks**.

**Code Example 3**: Memory leak caused by strings.  
[https://gist.github.com/hxzhouh/e09587195e2d7aa2d5f6676777c6cb16](https://gist.github.com/hxzhouh/e09587195e2d7aa2d5f6676777c6cb16?ref=huizhou92.com)  
[View code snippet on GitHub Gist →](https://gist.github.com/hxzhouh/e09587195e2d7aa2d5f6676777c6cb16?ref=huizhou92.com)

To prevent temporary memory leaks, we can use `strings.Clone()`.

**Code Example 4**: Using `strings.Clone()` to avoid temporary memory leaks.

```go
func Demo1() {  
    for i := 0; i < 10; i++ {  
       s := createStringWithLengthOnHeap(1 << 20) // 1MB  
       packageStr1 = append(packageStr1, strings.Clone(s[:50]))  
    }  
}

```

## Goroutine Leaks

### Goroutine Handler

Most memory leaks are due to `goroutine` leaks. For instance, the example below can quickly exhaust memory, resulting in an `OOM` (Out of Memory) error.

**Code Example 5**: `goroutine` handler leak.

```go
for {  
       go func() {  
          time.Sleep(1 * time.Hour)  
       }()  
    }  
}

```

### Misusing Channels

Incorrect usage of [channels](https://go.dev/ref/spec?ref=huizhou92.com#Channel%5Ftypes) can also easily lead to goroutine leaks. For unbuffered channels, both the producer and consumer must be ready before writing to the channel, or it will block. In the example below, the function exits early, causing a goroutine leak.

**Code Example 6**: Unbuffered channel misuse causes a goroutine leak.

```go
func Example() {
    a := 1
    c := make(chan error)
    go func() {
        c <- err
        return
    }()
    // Example exits here, causing a goroutine leak.
    if a > 0 {
        return
    }
    err := <-c
}

```

Simply changing it to a buffered channel can solve this issue: `c := make(chan error, 1)`.

### Misusing `range` with Channels

Channels can be iterated using `range`. However, if the channel is empty,the `range` will wait for new data, potentially blocking the `goroutine`.

**Code Example 7**: Misusing `range` causes a `goroutine` leak.

```go
func main() {  
    wg := &sync.WaitGroup{}  
    c := make(chan any, 1)  
    items := []int{1, 2, 3, 4, 5}  
    for _, i := range items {  
       wg.Add(1)  
       go func() {  
          c <- i  
       }()  
    }  
    go func() {  
       for data := range c {  
          fmt.Println(data)  
          wg.Done()  
       }  
       fmt.Println("close")  
    }()  
    wg.Wait()  
    time.Sleep(1 * time.Second)  
}

```

To fix this, close the channel after calling `wg.Wait()`.

## Misusing `runtime.SetFinalizer`

If two objects are both set with `runtime.SetFinalizer` and they reference each other, they will not be garbage collected, even if they're no longer in use. For more information, you can refer to my [other article](https://huizhou92.com/content/files/2026/09/decrypt-go-runtime-setfinalizer-adf19cb5fdcb-4.html).

## `time.Ticker`

This was an issue before Go 1.23\. If `ticker.Stop()` is not called, it could cause a memory leak. As of Go 1.23, this issue has been fixed. More details [here](https://go.dev/doc/go1.23?ref=huizhou92.com#timer-changes).

## Misusing `defer`

Although using `defer` to release resources does not directly cause memory leaks, it can lead to **temporary memory leaks** in two ways:

1. **Execution time**: `defer` is always executed when the function ends. If your function runs for too long or never ends, the resources in `defer` may not be released in time.
2. **Memory usage**: Each `defer` adds a call point in memory. If used inside a loop, this may cause a temporary memory leak.

**Code Example 8**: Temporary memory leak caused by `defer`.

```go
func ReadFile(files []string) {  
    for _, file := range files {  
	  f, err := os.Open(file)  
	  if err != nil {  
		 fmt.Println(err)  
		 return  
	  }  
	  // do something  
	  defer f.Close()  
    }  
}

```

This code causes a temporary memory leak and can lead to a "too many open files" error. Avoid using `defer` excessively unless necessary.

## Conclusion

This article covered several behaviors in Go that can lead to memory leaks, with `goroutine` leaks being the most common. The improper use of channels, especially with `select` and `range`, can make detecting leaks more difficult. When faced with memory leaks, [pprof](https://github.com/google/pprof?ref=huizhou92.com) can help quickly locate the problem, ensuring you write more robust code.

## References

- [https://go101.org/article/memory-leaking.html](https://go101.org/article/memory-leaking.html?ref=huizhou92.com)