What is the empty interface, and what are the tradeoffs of using it?

5 minintermediategointerfacesanygenerics

Quick Answer

interface{} (or its alias any, since Go 1.18) has zero methods, so every type satisfies it — it's Go's way of saying "a value of any type." It shows up in APIs like fmt.Println(v ...any) or map[string]any for arbitrary JSON-like data. The tradeoff: an any value loses all compile-time type information, so using it forces a type assertion or type switch to get anything useful back out, pushing type errors from compile time to runtime. Before generics arrived in Go 1.18, interface{} was the only way to write a function generic over multiple types (like a container), which meant giving up type safety entirely; generics now cover most of those cases with real compile-time type checking instead.

Detailed Answer

any is powerful precisely because it accepts anything, which is also exactly why it should be reached for carefully.

Where it's genuinely useful

func Println(a ...any) (n int, err error)   // fmt's real signature, roughly

data := map[string]any{
    "name": "Alice",
    "age":  30,
}  // a natural fit for arbitrary JSON-shaped data

The cost: you lose type safety

func process(v any) {
    // v could be anything — you must assert or switch to use it meaningfully
    switch x := v.(type) {
    case int:
        fmt.Println("int:", x*2)
    case string:
        fmt.Println("string:", x+"!")
    default:
        fmt.Println("unknown type")
    }
}

Nothing stops a caller from passing a completely unexpected type, and the compiler can't catch a mismatched type assertion — it only fails at runtime, often with a panic if you skip the safe two-value form.

Before generics: any as a workaround

// Pre-1.18: writing a generic-feeling container required any, and manual assertions
type Stack struct {
    items []any
}
func (s *Stack) Push(v any) { s.items = append(s.items, v) }
func (s *Stack) Pop() any {
    n := len(s.items) - 1
    v := s.items[n]
    s.items = s.items[:n]
    return v
}

With generics, the type-safe version

type Stack[T any] struct {
    items []T
}
func (s *Stack[T]) Push(v T) { s.items = append(s.items, v) }
func (s *Stack[T]) Pop() T {
    n := len(s.items) - 1
    v := s.items[n]
    s.items = s.items[:n]
    return v
}

var s Stack[int]
s.Push(5)
// s.Push("oops")  // compile error now, caught before runtime

The practical guidance

Reach for any when a value's type genuinely doesn't matter to your code (like a generic logging function), or when working with truly dynamic data (arbitrary JSON). Prefer generics when you want a container or algorithm that works over multiple types but should still be type-checked at compile time.

Related Resources