Under The Hood
backend go Go 1.21+ (sync.Pool)

sync.Pool: Thread-Local Caches, Victim Caches, and GC Drain

Last updated
Prerequisites:
Go goroutines and memory
Understanding of object reuse and GC
  • go
  • sync-pool
  • object-pool
  • gc
  • allocation
  • performance

Read at your depth

The practical view

sync.Pool reuses objects to reduce allocations: var pool = sync.Pool{ New: func() any { return &Buffer{} } }; b := pool.Get().(*Buffer); ...; pool.Put(b). Use it for frequently allocated, expensive-to-create objects (buffers, temporary structs). Critical rules: Get() may return nil if the pool is empty or was drained by GC (use New or handle nil); Put() returns an object for reuse; objects in the pool can be discarded at ANY time (especially after GC) — sync.Pool is NOT a persistent cache. It's for reducing allocation churn, not for guaranteeing object retention.

The same idea in other frameworks

react equivalent
N/A — Go runtime/library concern.

Legacy vs modern

Allocating per request vs reusing from a pool

Creating a new buffer per request drives allocation and GC; pooling reuses buffers with the same zero-value semantics via Reset.

before → after
Allocate a buffer per call
func handle(r io.Reader) error {
  buf := make([]byte, 32*1024) // ❌ OLD: new allocation per call
  ...
}
sync.Pool buffer reuse
var bufPool = sync.Pool{
  New: func() any { b := make([]byte, 32*1024); return &b }, // ✅ NEW
}

func handle(r io.Reader) error {
  buf := bufPool.Get().(*[]byte) // ✅ NEW: reuse
  defer bufPool.Put(buf)         // ✅ NEW: return for reuse
  ...
}

Interview gotchas

Context

This tests the pool lifecycle semantics — interviewers want the GC-drain behavior and why Pool is only for reducing allocation churn, not for caching.

The mechanical answer

The GC drains sync.Pool: on every GC cycle, the runtime moves the current pool objects to a victim cache and clears the live pools (the victim cache survives one more cycle). This means a pooled object can disappear at any collection — Get() may find the pool empty and return nil (or call New). Therefore sync.Pool guarantees NOTHING about retention: it is a best-effort reuse cache, designed purely to cut allocation rate for short-lived, frequently-created objects. It is the wrong tool for a persistent bounded cache (that's a purpose-built cache with eviction, or just holding references). Additionally, the pooled objects' state is whatever you left: you must Reset() them after Get if the previous user dirtied them, because Get returns the same object to different goroutines.

Trap

The naive answer is 'sync.Pool caches objects so they're always there'. GC can drain it at any moment — Get returning nil is normal. Another trap: using pooled objects without resetting — they carry the previous user's state, causing cross-request data leaks. Candidates who describe the GC drain and the reset requirement show real understanding.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.