What does sync.Pool's Get return when the pool is empty, and what is the New field for?
answer
- a cache, not storage
- Get hands you whatever it has
- empty pool needs a factory
- returns any, so you assert
- nil New means a nil interface
basics
~20 ssync.Pool.Get removes and returns some value that was previously handed to Put, if one is available. If the pool has nothing to give, it calls the pool's New function and returns that result. When New is nil, Get returns nil instead.
solid answer
~50 sA `sync.Pool` is a set of temporary, interchangeable objects that can be reused instead of allocated fresh each time. `Get` takes an arbitrary item out of the pool and returns it as `any`, so callers type-assert it back: `buf := bufPool.Get().(*bytes.Buffer)`. There is no relation between what you personally put in and what you get out — the pool is shared across goroutines, and the runtime is free to discard entries during garbage collection. If nothing is available, `Get` calls the `New func() any` field you set when declaring the pool; if you leave `New` nil, `Get` returns a nil interface and the type assertion panics, so in practice `New` is always set. `Put` hands an object back. A `sync.Pool` is safe for concurrent use by many goroutines and must not be copied after first use.
code
go · 14 linesvar bufPool = sync.Pool{
New: func() any {
return new(bytes.Buffer)
},
}
func encodeLine(msg string) string {
buf := bufPool.Get().(*bytes.Buffer)
defer bufPool.Put(buf)
buf.Reset()
buf.WriteString("msg=")
buf.WriteString(msg)
return buf.String() // String copies, so the result is safe to return
}go deeper
Be ready to write the three-line declaration from memory: a package-level pool, a New function returning a pointer, and a Get with a type assertion. Say out loud that Get may return a brand-new object.
Explain why Get's signature is any and what that costs when you pool a non-pointer, and why the pool must not be copied. Know that Put gives up ownership immediately.
Show judgment about what belongs in a pool at all: interchangeable scratch objects with no teardown, never anything whose disappearance would be a bug. Be able to justify adding one rather than reaching for it by reflex.
Frame it as an API contract question: a pool in a shared library imposes reset and no-retain rules on every caller. Decide whether that hazard is worth hiding behind your own Acquire/Release helpers instead of exposing the raw pool.
## The type `sync.Pool` is a small struct in the `sync` package with one field you fill in and two methods you call: - `New func() any` — a factory the pool calls when it has nothing to hand you. - `Get() any` — removes and returns an item. - `Put(x any)` — offers an item back for reuse. Its zero value is usable, so the idiom is a package-level `var bufPool = sync.Pool{New: func() any { return new(bytes.Buffer) }}`. The pool must not be copied after first use — it contains internal state and a `noCopy` marker, and `go vet`'s copylocks check reports a copy. ## What Get actually promises Very little, deliberately. `Get` selects an *arbitrary* item, removes it from the pool, and returns it. It may ignore whatever is stored and behave as if the pool were empty. Callers must not assume any relation between the values passed to `Put` and the values later returned by `Get`: an object you put may be taken by a different goroutine, or may be dropped entirely and never come back. That last part is the piece people are surprised by. `sync.Pool` is a cache, not storage. The runtime clears pooled entries as part of garbage collection; a victim generation softens the cliff so a burst of work spanning a collection does not lose everything at once, but the guarantee is still "maybe". Anything whose loss would be a correctness problem — an open file, a database connection, a sequence number — does not belong in a `sync.Pool`. ## Why New matters Because `Get` may legitimately return nothing, the pool needs a way to manufacture a replacement. That is `New`. With `New` set, `Get` never returns nil and calling code has exactly one shape: ```go buf := bufPool.Get().(*bytes.Buffer) ``` With `New` left nil, `Get` returns a nil `any` when the pool is empty and that type assertion panics at run time. You would then have to write the comma-ok form and a fallback allocation at every call site — which is what `New` exists to save you from. `New` may be called concurrently by several goroutines, so it must be safe to call at any time; in practice it is a one-line constructor with no shared state. ## The interface boxing detail `Get` returns `any` and `Put` accepts `any`, so every value that goes through the pool passes through an interface. For a pointer such as `*bytes.Buffer` that is free — a pointer fits in the interface word. For a non-pointer value such as a `[]byte` slice header, boxing it into an interface itself allocates, which quietly undoes part of the reason you reached for a pool. That is why the overwhelmingly common shape is a pool of pointers: `*bytes.Buffer`, `*strings.Builder`, or a pointer to a small struct wrapping a `[]byte`. ## Where it is used The canonical case is a scratch buffer on a hot path — for example, a logging library that formats every line into a `bytes.Buffer` before writing it out. Each call needs a buffer, uses it for microseconds, and throws it away; recycling a handful of them across all callers is much cheaper than allocating one per line. The standard library uses the same technique inside its own formatters. ## The rules that come with it Three, and all three follow from the contract above. First, always set `New`. Second, treat an object you hand to `Put` as gone — another goroutine may already be writing into it, so keep no references to it or to any slice that aliases its memory. Third, since the pool hands you whatever the last user left behind, reset the value's state before you use it (or immediately before you put it back), consistently, in exactly one place.
- Why do people pool *bytes.Buffer rather than a bytes.Buffer value or a plain []byte?Because `Put` takes `any`. A pointer fits in an interface word for free, while boxing a value type or a slice header into an interface allocates — exactly the allocation the pool was meant to avoid. Pooling a pointer also means the caller mutates the pooled object in place rather than a copy, which is the whole point.
- What happens if you copy a sync.Pool, for example by storing it in a struct that is passed by value?It breaks: the pool carries internal per-processor state that must not be duplicated, and the documentation says a Pool must not be copied after first use. `go vet`'s copylocks analyzer flags the copy at build time, and `go test` runs vet by default, so this normally fails before it ships. Hold a `*sync.Pool` or make the pool a package-level variable.
- Is it safe for many goroutines to call Get and Put on the same sync.Pool at once?Yes — that is what it is designed for, and no external locking is needed. Internally it keeps per-processor storage so concurrent callers rarely contend. What is *not* safe is continuing to use an object after you have handed it to `Put`; at that moment another goroutine may already own it.
A tray of scratch paper by the printer. You take a sheet if one is there; if the tray is empty someone fetches a fresh sheet. Nobody promises the sheet you left is the sheet you get back.
saying these in an interview costs you the question
- Assumes Get returns the same object you put in
- Thinks objects stay in the pool until you take them out
- Leaves New nil and is surprised by a nil type assertion panic
- Calls Get expecting a concrete type without a type assertion
- Stores a sync.Pool by value inside a copied struct