ProgrammingBackend Developer

What is a goroutine leak in Go and how to prevent it?

Pass interviews with Hintsage AI assistant

Answer.

Background:

A goroutine leak is a situation where a goroutine continues to exist and hangs in memory, even though it has effectively "lost its purpose" (calculations are complete, data is no longer needed, but there is no exit condition). Similar to memory leaks, but for execution threads. This is critical for Go, as high load can lead to resource exhaustion.

Problem:

Unlike other languages, where direct thread management often leads to their manual closure, in Go goroutines are easily launched but not always correctly terminated. A common mistake: the main logic has finished, but the goroutine is "frozen" — waiting for data on a closed channel or may never receive a signal.

Solution:

To prevent leaks, controlling constructs such as context, channel closure, and signaling variables are used. It is important to design exit paths for each goroutine in advance and to use defer for cleanup. Example:

func worker(ctx context.Context, jobs <-chan int, results chan<- int) { for { select { case <-ctx.Done(): return case job, ok := <-jobs: if !ok { return } results <- job * 2 } } }

Key Features:

  • Control the entire lifecycle of a goroutine
  • Use context for managed termination
  • Close channels after use

Tricky Questions.

Can you just close the channel to stop a goroutine?

Not always. If there are other cases in select or there is no closure check via ok, the goroutine may remain "hanging".

val, ok := <-ch if !ok { return } // this is the correct way

What will happen if you forget to handle context.Done in select?

The goroutine will never know that cancellation occurred — it will remain "eternal". This is a direct path to a leak.

Can you catch a leak using go runtime?

There is no standard tool for tracking leaks. It is necessary to monitor the number of active goroutines through runtime.NumGoroutine or use leak detectors from third-party libraries.

Typical Mistakes and Anti-patterns

  • Waiting on a nonexistent or blocked channel
  • Launching infinite goroutines without exit paths
  • Inconsistency in closing channels

Real-life Example

Negative Case

In a push notification system, goroutines are launched for each incoming message but forget to stop them when the context is canceled or the channel is closed — hundreds of "dead" goroutines hang in memory.

Pros:

  • Easy to start, quick prototyping

Cons:

  • Memory growth
  • Scheduler slowdown

Positive Case

The work of the goroutine is controlled through context, the exit from select is checked at the business logic level, and the channel is closed after successful sending/processing.

Pros:

  • No leaks
  • Easy to monitor and profile

Cons:

  • Requires careful design of channels and goroutines