Reflection Basics
10 examples to get you started with Reflection & Codegen - 7 basic and 3 intermediate.
Prerequisites
- Install Go 1.26.x or later from go.dev/dl.
- Create a module:
mkdir rfdemo && cd rfdemo && go mod init example.com/rfdemo. - Save snippets as
main.goand run withgo run ..
Basic Examples
1. reflect.TypeOf for static shape
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())
}TypeOfneeds 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
2. reflect.ValueOf for runtime data
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())
}ValueOfreturns areflect.Valueholding a copy ofs(not addressable).CanSet()is false for values not passed by pointer.- Use
Kind()to branch before calling type-specific methods.
Related: Deep Equality & Kind Switches - safe dispatch by kind
3. Pointer to make a Value settable
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.SetIntworks only whenKind() == reflect.IntandCanSet()is true.- Mutating through reflection updates the original variable when addressable.
4. Iterating struct fields
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())
}
}FieldreturnsStructFieldwith name, type, tag, and offset metadata.Tag.Get("json")reads one key from the struct tag string.- Unexported fields are visible on types defined in the same package.
Related: Struct Tags & Custom Tag Parsing - parsing validate and db tags
5. FieldByName on a Value
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())
}FieldByNameon a non-pointer structValuereturns a non-settable field.- Use
reflect.ValueOf(&p).Elem()when you needSet. - Missing names return an invalid
Value; checkIsValid()before use.
6. Kind and Elem for pointers
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())
}- A nil pointer still has type
*int;ValueOf(nil)needs care (see gotchas in sibling pages). Elem()on*TyieldsT; on slices it yields element type.- Generics do not change reflection APIs;
TypeOfsees instantiated types.
7. Interface() to recover an any
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.- Prefer typed assertions after
Interface()when you know the type. - This round-trip is how many codecs pass decoded data back to user code.
Intermediate Examples
8. Walk nested structs with recursion
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"}}))
}- Pass struct values, not pointers, unless you need to mutate fields.
- Skip
time.Timeand similar types if you do not want to descend into them. - This pattern powers struct-to-map serializers and debug printers.
Related: Building Custom Code Generators - replace runtime walks with generated code
9. Set fields from a map[string]string
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)
}- Only set fields when
CanSet()is true (pointer to struct required). - Production parsers add per-type converters and error aggregation.
- Codegen can emit a switch per field for faster startup binding.
Related: Struct Tags & Custom Tag Parsing - robust tag parsing
10. Allocate a new pointer with reflect.New
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())
}Newalways returns a pointerValuesettable viaElem().- Frameworks use this to instantiate
*Userfromreflect.TypeOf(User{}). - Prefer concrete
new(T)in application code;reflect.Newshines 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).