Reflection Basics
10 examples to get you started with Reflection & Codegen - 7 basic and 3 intermediate.
Search across all documentation pages
10 examples to get you started with Reflection & Codegen - 7 basic and 3 intermediate.
mkdir rfdemo && cd rfdemo && go mod init example.com/rfdemo.main.go and run with go run ..TypeOf returns the compile-time type of a value as a reflect.Type.
package main
import (
"fmt"
"reflect"
)
func main() {
var n int = 42
t := reflect.TypeOf(n)
fmt.Println(t.Name(), t.Kind(), t.Size())
}TypeOf needs a value; use a typed variable or literal.Kind() is the underlying kind (Int, Struct, Ptr, etc.), not the named type.Size() reports bytes the type occupies in memory.Related: Reflection vs Code Generation in Go - when runtime inspection fits
ValueOf wraps the actual data so you can read or mutate it when settable.
package main
import (
"fmt"
"reflect"
)
func main() {
s := "hello"
v := reflect.ValueOf(s)
fmt.Println(v.String(), v.Kind(), v.CanSet())
}ValueOf returns a reflect.Value holding a copy of s (not addressable).CanSet() is false for values not passed by pointer.Kind() to branch before calling type-specific methods.Related: Deep Equality & Kind Switches - safe dispatch by kind
Pass a pointer to ValueOf, then Elem() to reach the underlying value.
package main
import (
"fmt"
"reflect"
)
func main() {
x := 10
v := reflect.ValueOf(&x).Elem()
fmt.Println("before", x, v.CanSet())
v.SetInt(99)
fmt.Println("after", x)
}Elem() dereferences pointers, slices, arrays, maps, and channels.SetInt works only when Kind() == reflect.Int and CanSet() is true.Loop NumField and Field(i) to inspect every exported and unexported field of a struct type.
package main
import (
"fmt"
"reflect"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
age int // unexported
}
func main() {
t := reflect.TypeOf(User{})
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
fmt.Printf("%s: tag=%q exported=%v\n", f.Name, f.Tag.Get("json"), f.IsExported())
}
}Field returns StructField with name, type, tag, and offset metadata.Tag.Get("json") reads one key from the struct tag string.Related: Struct Tags & Custom Tag Parsing - parsing validate and db tags
Look up a field by name on a struct Value (not just Type).
package main
import (
"fmt"
"reflect"
)
type Point struct{ X, Y int }
func main() {
p := Point{3, 4}
v := reflect.ValueOf(p)
fx := v.FieldByName("X")
fmt.Println(fx.Int())
}FieldByName on a non-pointer struct Value returns a non-settable field.reflect.ValueOf(&p).Elem() when you need Set.Value; check IsValid() before use.Inspect pointer types with Kind() == reflect.Ptr and Elem() for the pointed-to type.
package main
import (
"fmt"
"reflect"
)
func main() {
var p *int
t := reflect.TypeOf(p)
fmt.Println(t.Kind(), t.Elem().Kind())
}*int; ValueOf(nil) needs care (see gotchas in sibling pages).Elem() on *T yields T; on slices it yields element type.TypeOf sees instantiated types.Convert a Value back to interface{} / any when the dynamic type is exported.
package main
import (
"fmt"
"reflect"
)
func main() {
v := reflect.ValueOf([]int{1, 2, 3})
any := v.Interface()
slice := any.([]int)
fmt.Println(slice)
}Interface() panics if the value contains unexported fields and you cross package boundaries.Interface() when you know the type.Combine Kind, NumField, and Field to flatten paths like Address.City.
package main
import (
"fmt"
"reflect"
)
type Address struct{ City string }
type Person struct {
Name string
Addr Address
}
func walk(prefix string, v reflect.Value) {
t := v.Type()
for i := 0; i < v.NumField(); i++ {
f := t.Field(i)
fv := v.Field(i)
path := f.Name
if prefix != "" {
path = prefix + "." + f.Name
}
if fv.Kind() == reflect.Struct {
walk(path, fv)
continue
}
fmt.Println(path, fv.Interface())
}
}
func main() {
walk("", reflect.ValueOf(Person{Name: "Ada", Addr: Address{City: "London"}}))
}time.Time and similar types if you do not want to descend into them.Related: Building Custom Code Generators - replace runtime walks with generated code
Bind external key/value data onto struct fields using tags or names.
package main
import (
"fmt"
"reflect"
"strings"
)
type Config struct {
Host string `env:"HOST"`
Port int `env:"PORT"`
}
func applyEnv(cfg any, env map[string]string) error {
v := reflect.ValueOf(cfg)
if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
return fmt.Errorf("cfg must be *struct")
}
v = v.Elem()
t := v.Type()
for i := 0; i < t.NumField(); i++ {
key := t.Field(i).Tag.Get("env")
if key == "" {
continue
}
raw, ok := env[key]
if !ok {
continue
}
fv := v.Field(i)
if fv.Kind() == reflect.String {
fv.SetString(raw)
}
if fv.Kind() == reflect.Int {
var n int
fmt.Sscanf(raw, "%d", &n)
fv.SetInt(int64(n))
}
}
return nil
}
func main() {
c := &Config{}
_ = applyEnv(c, map[string]string{"HOST": "localhost", "PORT": "8080"})
fmt.Println(c.Host, c.Port)
}CanSet() is true (pointer to struct required).Related: Struct Tags & Custom Tag Parsing - robust tag parsing
reflect.New(t) creates a *T with zero value, useful for decoding into unknown shapes.
package main
import (
"fmt"
"reflect"
)
func main() {
t := reflect.TypeOf(0)
ptr := reflect.New(t) // *int
ptr.Elem().SetInt(7)
fmt.Println(ptr.Elem().Int())
}New always returns a pointer Value settable via Elem().*User from reflect.TypeOf(User{}).new(T) in application code; reflect.New shines in generic libraries.Related: ORM & Serializer Reflection Costs - allocation patterns in codecs
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 18, 2026