Reflection in Go allows programs to examine and manipulate their own structure at runtime. It's a powerful but complex feature that should be used judiciously.
What is reflection?
Reflection is the ability of a program to examine its own structure, particularly types and values, at runtime. In Go, reflection is provided by the reflect package.
Go is statically typed—types are known at compile time. However, sometimes you need to work with types that aren't known until runtime. Reflection enables serialization and deserialization (converting data to/from JSON, XML, etc.), generic operations on values of unknown types, framework code that needs to work with arbitrary types, and testing utilities that work with any type.
Reflection in Go is based on two main types: reflect.Type, which represents a Go type, and reflect.Value, which represents a Go value.
Basic example
package main
import (
"fmt"
"reflect"
)
func main() {
x := 42
v := reflect.ValueOf(x)
t := reflect.TypeOf(x)
fmt.Println("Type:", t) // int
fmt.Println("Kind:", t.Kind()) // int
fmt.Println("Value:", v.Int()) // 42
}
Reflection is powerful because it enables generic operations on unknown types, but it's also dangerous because it bypasses type safety, can cause panics, and is slower than direct operations. Use reflection sparingly—only when necessary, as it makes code harder to understand and maintain.
Understanding reflect.Type and reflect.Value is fundamental to using reflection.
reflect.Type
reflect.Type represents a Go type. You can get it using reflect.TypeOf().
Getting the type of a value:
package main
import (
"fmt"
"reflect"
)
func main() {
var x int = 42
var s string = "hello"
var f float64 = 3.14
tx := reflect.TypeOf(x)
ts := reflect.TypeOf(s)
tf := reflect.TypeOf(f)
fmt.Println("Type of x:", tx) // int
fmt.Println("Type of s:", ts) // string
fmt.Println("Type of f:", tf) // float64
}
Type information
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
Age int
}
func main() {
p := Person{Name: "Alice", Age: 30}
t := reflect.TypeOf(p)
fmt.Println("Type name:", t.Name()) // Person
fmt.Println("Kind:", t.Kind()) // struct
fmt.Println("Number of fields:", t.NumField()) // 2
// Iterate over fields
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("Field %d: %s (type: %s)\n",
i, field.Name, field.Type)
}
}
reflect.Value
reflect.Value represents a Go value. You can get it using reflect.ValueOf().
Getting the value:
package main
import (
"fmt"
"reflect"
)
func main() {
x := 42
v := reflect.ValueOf(x)
fmt.Println("Value:", v)
fmt.Println("Type:", v.Type())
fmt.Println("Kind:", v.Kind())
fmt.Println("Can set:", v.CanSet()) // false (value is not settable)
}
When you call reflect.ValueOf() on a value (not a pointer), you get a copy. To modify the original, you need a pointer:
package main
import (
"fmt"
"reflect"
)
func main() {
x := 42
// Not settable (value, not pointer)
v1 := reflect.ValueOf(x)
fmt.Println("Can set v1:", v1.CanSet()) // false
// Settable (pointer)
v2 := reflect.ValueOf(&x)
fmt.Println("Can set v2:", v2.CanSet()) // false (pointer itself)
// Need to get element of pointer
v3 := v2.Elem()
fmt.Println("Can set v3:", v3.CanSet()) // true
// Now we can set
v3.SetInt(100)
fmt.Println("x after setting:", x) // 100
}
Modifying values:
package main
import (
"fmt"
"reflect"
)
func main() {
x := 42
v := reflect.ValueOf(&x).Elem() // Get settable value
if v.CanSet() {
v.SetInt(100)
fmt.Println("x:", x) // 100
}
}
Reflection is commonly used in several scenarios.
1. JSON serialization/deserialization
The encoding/json package uses reflection to marshal and unmarshal data:
package main
import (
"encoding/json"
"fmt"
"reflect"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func inspectStruct(v interface{}) {
t := reflect.TypeOf(v)
if t.Kind() != reflect.Struct {
return
}
fmt.Printf("Struct: %s\n", t.Name())
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
jsonTag := field.Tag.Get("json")
fmt.Printf(" Field: %s, JSON tag: %s\n", field.Name, jsonTag)
}
}
func main() {
p := Person{Name: "Alice", Age: 30}
inspectStruct(p)
// JSON uses reflection internally
data, _ := json.Marshal(p)
fmt.Println("JSON:", string(data))
}
2. Type checking and conversion
package main
import (
"fmt"
"reflect"
)
func processValue(v interface{}) {
val := reflect.ValueOf(v)
switch val.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
fmt.Printf("Integer: %d\n", val.Int())
case reflect.String:
fmt.Printf("String: %s\n", val.String())
case reflect.Bool:
fmt.Printf("Bool: %t\n", val.Bool())
default:
fmt.Printf("Other type: %s\n", val.Type())
}
}
func main() {
processValue(42)
processValue("hello")
processValue(true)
processValue(3.14)
}
3. Calling functions dynamically
package main
import (
"fmt"
"reflect"
)
func Add(a, b int) int {
return a + b
}
func Multiply(a, b int) int {
return a * b
}
func callFunction(fn interface{}, args ...interface{}) []reflect.Value {
v := reflect.ValueOf(fn)
if v.Kind() != reflect.Func {
panic("not a function")
}
// Convert args to reflect.Values
in := make([]reflect.Value, len(args))
for i, arg := range args {
in[i] = reflect.ValueOf(arg)
}
// Call function
return v.Call(in)
}
func main() {
result := callFunction(Add, 10, 20)
fmt.Println("Add(10, 20) =", result[0].Int()) // 30
result = callFunction(Multiply, 5, 6)
fmt.Println("Multiply(5, 6) =", result[0].Int()) // 30
}
4. Creating values dynamically
package main
import (
"fmt"
"reflect"
)
func createSlice(t reflect.Type, length, capacity int) reflect.Value {
sliceType := reflect.SliceOf(t)
return reflect.MakeSlice(sliceType, length, capacity)
}
func main() {
// Create []int with length 5, capacity 10
intSliceType := reflect.TypeOf(0) // int type
slice := createSlice(intSliceType, 5, 10)
// Convert back to []int
intSlice := slice.Interface().([]int)
fmt.Println("Slice:", intSlice) // [0 0 0 0 0]
fmt.Println("Length:", slice.Len())
fmt.Println("Capacity:", slice.Cap())
}
5. Struct field access
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
Age int
}
func getFieldValue(v interface{}, fieldName string) (interface{}, bool) {
val := reflect.ValueOf(v)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
if val.Kind() != reflect.Struct {
return nil, false
}
field := val.FieldByName(fieldName)
if !field.IsValid() {
return nil, false
}
return field.Interface(), true
}
func main() {
p := Person{Name: "Alice", Age: 30}
name, ok := getFieldValue(p, "Name")
if ok {
fmt.Println("Name:", name) // Alice
}
age, ok := getFieldValue(&p, "Age")
if ok {
fmt.Println("Age:", age) // 30
}
}
When reflection is appropriate
When NOT to use reflection
Example: When NOT to use reflection
// ❌ BAD: Using reflection unnecessarily
func addReflection(a, b interface{}) interface{} {
va := reflect.ValueOf(a)
vb := reflect.ValueOf(b)
if va.Kind() == reflect.Int && vb.Kind() == reflect.Int {
return va.Int() + vb.Int()
}
// ... more reflection code
}
// ✅ GOOD: Use generics instead (Go 1.18+)
func add[T int | float64](a, b T) T {
return a + b
}
// Or use interfaces for runtime polymorphism
type Adder interface {
Add(other Adder) Adder
}
Reflection has significant performance overhead compared to direct operations.
Reflection has significant performance overhead because types must be checked at runtime, there are extra layers of indirection, the compiler can't optimize reflected code, and reflection often allocates memory.
Performance comparison
package main
import (
"reflect"
"testing"
)
type Person struct {
Name string
Age int
}
// Direct access
func BenchmarkDirect(b *testing.B) {
p := Person{Name: "Alice", Age: 30}
for i := 0; i < b.N; i++ {
_ = p.Name
_ = p.Age
}
}
// Reflection access
func BenchmarkReflection(b *testing.B) {
p := Person{Name: "Alice", Age: 30}
v := reflect.ValueOf(p)
for i := 0; i < b.N; i++ {
_ = v.FieldByName("Name").String()
_ = v.FieldByName("Age").Int()
}
}
Typical performance impact
To minimize reflection overhead, cache reflect.Type and reflect.Value instead of recreating them in loops, use type assertions when possible (they're faster than reflection), limit reflection scope to only where necessary, and consider code generation to create type-specific code instead.
Example: Caching reflection values
// ❌ BAD: Recreating reflection values
func slowProcess(v interface{}) {
for i := 0; i < 1000; i++ {
t := reflect.TypeOf(v) // Recreated every iteration
val := reflect.ValueOf(v)
// ... use t and val
}
}
// ✅ GOOD: Cache reflection values
var cachedType = reflect.TypeOf((*MyType)(nil)).Elem()
var cachedValue reflect.Value
func fastProcess(v interface{}) {
// Cache once
if !cachedValue.IsValid() {
cachedValue = reflect.New(cachedType).Elem()
}
// Reuse cached values
}
Best practices
Reflection is a powerful tool, but it should be used judiciously. When possible, prefer type-safe alternatives like generics or interfaces. Use reflection when you truly need to work with unknown types at runtime, such as in serialization libraries or framework code.