any (an alias for interface{}) holds a value of any type. Reflection, through the reflect package, lets you inspect that value’s type and fields at runtime — read struct tags, call methods by name, compare unknown types, or build generic serialization logic.
Both are powerful and both are overused. Before reaching for either, check whether a typed interface or generics (Go 1.18+) would be clearer. Reflection bypasses the type system — it’s correct Go but harder to read and slower to execute than typed code.
For type assertions and type switches on interface values see Go type assertions and switches.
any / interface{}
any is just interface{} — an alias introduced in Go 1.18. An any variable can hold any value, but to do anything type-specific with it, you need a type assertion or type switch:
var v any = 42
// Type assertion — extracts the concrete value
n, ok := v.(int)
if ok {
fmt.Println(n * 2) // 84
}
// Type switch — handles multiple possible types
switch val := v.(type) {
case int: fmt.Println("int:", val)
case string: fmt.Println("string:", val)
default: fmt.Println("other:", val)
}
any is the right choice for genuinely heterogeneous collections and for functions that must accept values of truly unknown type — JSON unmarshaling into map[string]any, for example. For everything else, a typed interface or generic function is clearer and faster.
reflect.TypeOf and reflect.ValueOf
These two functions are the entry points to the reflection system:
x := 42
fmt.Println(reflect.TypeOf(x)) // int
fmt.Println(reflect.TypeOf(x).Kind()) // int (same here, differs for named types)
s := "hello"
fmt.Println(reflect.TypeOf(s)) // string
fmt.Println(reflect.TypeOf(s).Kind()) // string
type Celsius float64
c := Celsius(37.0)
fmt.Println(reflect.TypeOf(c)) // main.Celsius
fmt.Println(reflect.TypeOf(c).Kind()) // float64 ← underlying kind
Type is the Go type name (main.Celsius). Kind is the underlying kind (float64). When writing general reflection code that branches on numeric operations, check Kind, not Type.
reflect.ValueOf wraps a value in a reflect.Value that exposes methods to inspect and manipulate it:
v := reflect.ValueOf(42)
fmt.Println(v.Type()) // int
fmt.Println(v.Kind()) // int
fmt.Println(v.Int()) // 42
s := reflect.ValueOf("hello")
fmt.Println(s.String()) // hello
fmt.Println(s.Len()) // 5
Inspecting Structs
The most common practical use of reflection is reading struct fields and their tags — this is how encoding/json, GORM, and go-playground/validator work:
type User struct {
ID int `json:"id" db:"user_id"`
Name string `json:"name" validate:"required"`
Email string `json:"email,omitempty"`
}
func inspectStruct(v any) {
t := reflect.TypeOf(v)
val := reflect.ValueOf(v)
// Dereference pointer if needed
if t.Kind() == reflect.Ptr {
t = t.Elem()
val = val.Elem()
}
if t.Kind() != reflect.Struct {
fmt.Println("not a struct")
return
}
fmt.Printf("struct %s has %d fields:\n", t.Name(), t.NumField())
for i := 0; i < t.NumField(); i++ {
field := t.Field(i) // reflect.StructField — type info + tags
value := val.Field(i) // reflect.Value — the actual value
jsonTag := field.Tag.Get("json")
fmt.Printf(" %s (type %s) = %v [json:%q]\n",
field.Name, field.Type, value.Interface(), jsonTag)
}
}
u := User{ID: 1, Name: "Alice", Email: "[email protected]"}
inspectStruct(u)
// struct User has 3 fields:
// ID (type int) = 1 [json:"id"]
// Name (type string) = Alice [json:"name"]
// Email (type string) = [email protected] [json:"email,omitempty"]
field.Tag.Get("json") returns the value of the json struct tag for that field. This is exactly how encoding/json decides what JSON key name to use.
Modifying Values via Reflection
Reflection can modify values, but only through a pointer. Passing a value (not pointer) to reflect.ValueOf gives you an unaddressable copy — trying to set it panics:
type Config struct {
Host string
Port int
}
cfg := Config{Host: "localhost", Port: 8080}
// ❌ Can't set — cfg is passed by value, fields are unaddressable
v := reflect.ValueOf(cfg)
v.FieldByName("Host").SetString("example.com") // panics: reflect: reflect.Value.SetString using value obtained using unexported field
// ✅ Pass a pointer and call Elem() to get the addressable struct
v = reflect.ValueOf(&cfg).Elem()
field := v.FieldByName("Host")
if field.IsValid() && field.CanSet() {
field.SetString("example.com")
}
field = v.FieldByName("Port")
if field.IsValid() && field.CanSet() {
field.SetInt(9090)
}
fmt.Println(cfg) // {example.com 9090}
Always check field.IsValid() (the field exists) and field.CanSet() (the field is exported and addressable) before calling any Set method. Skipping either check causes panics.
Calling Methods via Reflection
type Greeter struct{ Name string }
func (g Greeter) Hello() string {
return "Hello, " + g.Name
}
g := Greeter{Name: "Alice"}
v := reflect.ValueOf(g)
method := v.MethodByName("Hello")
if method.IsValid() {
results := method.Call(nil) // nil = no arguments
fmt.Println(results[0].String()) // "Hello, Alice"
}
// With arguments
type Math struct{}
func (m Math) Add(a, b int) int { return a + b }
m := reflect.ValueOf(Math{})
addMethod := m.MethodByName("Add")
args := []reflect.Value{reflect.ValueOf(3), reflect.ValueOf(4)}
result := addMethod.Call(args)
fmt.Println(result[0].Int()) // 7
Method reflection is how RPC frameworks, test frameworks, and plugin systems work — calling methods whose names are only known at runtime.
Reflection vs Generics
Go 1.18 added generics, which solve many problems that previously required interface{} and reflection. Generics are type-safe and typically faster:
// Before generics: required reflection or interface{} + type assertions
func containsOld(slice []interface{}, item interface{}) bool {
for _, v := range slice {
if reflect.DeepEqual(v, item) { return true }
}
return false
}
// With generics: type-safe, no reflection needed, works with any comparable type
func Contains[T comparable](slice []T, item T) bool {
for _, v := range slice {
if v == item { return true }
}
return false
}
Contains([]int{1, 2, 3}, 2) // true
Contains([]string{"a", "b"}, "c") // false
When to use reflection instead of generics:
- Reading and writing struct tags — generics can’t inspect tags
- Building serializers/deserializers for arbitrary struct types
- Implementing ORMs or dependency injection containers
- Plugin systems where types are truly unknown at compile time
When to prefer generics:
- Type-flexible data structures (stacks, queues, sets)
- Functions that work uniformly on multiple types
- Any case where the types are constrained but flexible
Performance: Reflection Is Slow
Reflection bypasses compiler optimizations and involves interface value allocation. It’s roughly 10–50x slower than equivalent direct code:
// Direct: nanoseconds
n := myStruct.Field
// Reflection: microseconds
v := reflect.ValueOf(myStruct).FieldByName("Field").Int()
Cache reflection results when performance matters. encoding/json caches the type analysis (field list, struct tags, method lookup) in a sync.Map so parsing a given struct type only happens once per program run.
Summary
any(=interface{}) holds any value; type assertions and type switches extract the concrete typereflect.TypeOfgives the Go type name and kind;reflect.ValueOfgives a manipulable wrapper- Struct inspection via
t.NumField(),t.Field(i),field.Tag.Get("json")— the foundation of serialization libraries - Always check
field.CanSet()before calling Set methods; pass a pointer and call.Elem()to get addressable fields - Prefer generics for type-flexible functions (Go 1.18+); use reflection only when struct tags or unknown-at-compile-time types are genuinely required
- Cache reflection results for performance — reflection is 10–50x slower than direct field access
Resources
- reflect package documentation
- Go Blog: The Laws of Reflection
- Go Blog: An Introduction to Generics
- Go by Example: Generics
Comments