Go

Does converting a value to an interface allocate? How do interfaces affect escape analysis and performance?

Question 100HardGo 1.22 to 1.25

Often it does. The data word must be a pointer, so non-pointer values are copied into memory. The runtime helpers (convT, convT64, convTstring) allocate on the heap unless one of these holds:

  • The value is already pointer-shaped (*T, map, chan, func).
  • The value is zero-sized.
  • The value is a constant or a small integer under 256 (the runtime uses the static array staticuint64s).
  • Escape analysis proves the interface does not escape, so the copy can live on the stack.
var sink any // package-level, so the boxed value escapes

func BenchmarkBox(b *testing.B) {
    for i := range b.N {
        sink = i + 1000 // 1 alloc/op (8 B): int above 255 boxed on heap
    }
}
// With a local sink the interface would not escape, the box would live on
// the stack, and the benchmark would report 0 allocs/op.

// Check what escapes:
//   go build -gcflags='-m' ./...
//   ./x.go:12:9: i + 1000 escapes to heap

Calling through an interface also hides the callee from the compiler. Arguments passed to interface methods are usually assumed to escape. That is why fmt.Println(x) makes x escape, and why w.Write(buf) through an io.Writer forces buf onto the heap.

Ways to mitigate in hot paths:

  • Use concrete types or generics. GC-shape stenciling still uses dictionaries, but it avoids boxing.
  • Pass pointers.
  • Avoid any-based variadic loggers in tight loops, or use slog's typed Attr helpers.
  • Enable PGO so hot call sites get devirtualized.

Always measure with -benchmem before optimising.

More on Interfaces, Methods & Embedding

All 35 Interfaces, Methods & Embedding questions