any vs interface{} in Go: What’s the Real Difference?
Ethan Miller
Product Engineer · Leapcell

any
vs interface{}
in Go: What's the Difference?
In a recent team discussion, someone claimed "any
and interface{}
in Go are identical." Technically, they're right—Go's official definition makes any
an alias for interface{}
:
type any = interface{}
They're functionally equivalent under the hood. So why did the Go team introduce any
? Semantics and readability:
interface{}
: Represents "unconstrained dynamic types" (think JSON parsing, reflection). Emphasizes dynamism, requires type assertions.any
: Designed specifically for generics. Marks unconstrained type parameters, emphasizing type-safe generality—once a type is set, it stays consistent.
This distinction preserves backward compatibility while clarifying generics code.
The Problem Go Generics Solve
Before Go 1.18 (when generics arrived), developers repeated code for identical logic with different types. Example: summing number slices:
// Sum []int64 func SumInts(numbers []int64) int64 { var s int64 for _, v := range numbers { s += v } return s } // Sum []float64 (same logic, different type) func SumFloats(numbers []float64) float64 { var s float64 for _, v := range numbers { s += v } return s }
This violates DRY (Don't Repeat Yourself) and increases maintenance costs.
Go Generics: Core Concepts
Go 1.18 introduced generics with 3 key ideas:
- Type Parameters: Let functions/types use parameterized types.
- Type Constraints: Define valid types for parameters via interfaces.
- Type Inference: Automatically deduces types during calls, simplifying code.
Refactoring the Sum Function with Generics
// Generic function: works for int64 or float64 func SumNumbers[T int64 | float64](numbers []T) T { var s T for _, v := range numbers { s += v } return s }
[T int64 | float64]
declares the type parameter:T
is the type variable,int64 | float64
is the union constraint.- No need to specify types when calling (compiler infers them):
ints := []int64{1, 2, 3} floats := []float64{1.1, 2.2, 3.3} fmt.Println(SumNumbers(ints)) // 6 fmt.Println(SumNumbers(floats)) // 6.6
Reusable Type Constraints
For complex/reusable constraints, define them as interfaces:
// Define a constraint for numbers type Number interface { int64 | float64 } // Refactored with custom constraint func SumNumbers[T Number](numbers []T) T { var s T for _, v := range numbers { s += v } return s }
This boosts readability and maintainability.
How Go Generics Work: Performance Balance
Go's generics are efficient thanks to a unique approach: GC Shape Monomorphization + Dictionaries (not reflection or v-tables):
-
GC Shape Monomorphization
The compiler generates code based on a type's "GC shape" (size, alignment, pointer presence). For example:int32
,uint32
,float32
share the same shape (4 bytes, no pointers) → reuse code.- All pointer types (
*int
,*string
) share a shape → reuse code.
This avoids "code bloat" while matching native performance.
-
Dictionary Technique
For types with the same shape but different behavior (e.g.,int
vsfloat32
addition), the compiler uses hidden "dictionaries" to pass type-specific info (method addresses, operation functions).
Real-World Impact for Developers
- Near-Native Performance: Arithmetic operations match non-generic code speed. Minimal overhead for dictionary-based method calls (on par with interface calls).
- Controlled Binary Size: Code reuse for same "GC shape" types prevents bloat.
Conclusion
any
and interface{}
are technically equivalent, but their semantics signal Go's type system evolution: interface{}
for dynamic typing, any
for generics. Understanding this helps write clearer, more idiomatic Go.
Leapcell: The Best Serverless Web Hosting
Recommended platform for deploying Go services: Leapcell
🚀 Build with Your Favorite Language
Develop effortlessly in JavaScript, Python, Go, or Rust.
🌍 Deploy Unlimited Projects for Free
Only pay for what you use—no requests, no charges.
⚡ Pay-as-You-Go, No Hidden Costs
No idle fees, just seamless scalability.
🔹 Follow on Twitter: @LeapcellHQ