Golang reflect is the standard reflect package. People also say golang reflection, reflect golang, go reflection, or goreflect when they mean the same thing: inspect or manipulate types and values at run time, usually starting from an any / interface{}. Go's model is summarized in The Laws of Reflection: reflection moves between interface values and reflect.Type / reflect.Value, and only settable Values can be written through. This guide goes deeper than a short "TypeOf and walk a struct" article: it covers Elem, Set, New, MakeSlice / MakeMap / MakeChan, MakeFunc, StructOf, Call, Append, DeepEqual, limits (no dynamic interface implementation), and when generics replace reflection. For lighter type printing without walking fields, see print type in Go; for interfaces without reflect, see interfaces in Go and type assertions.
Checked with Go 1.24 on 64-bit Linux. Long snippets use
{run=false}so you can paste them locally; short programs match a singlemain.golayout.
Laws of reflection and the three objects
Rob Pike's The Laws of Reflection is still the best mental model:
- Reflection starts from an interface value (including
any) and produces reflection objects (Type,Value). - You can turn a
Valueback intoanywithInterface(), losing static type information until you assert or switch. - To mutate through
Value, the value must be settable (CanSet): almost always pass a pointer toValueOf, thenElem()to reach the struct or scalar inside.
reflect.TypeOf describes layout and names; reflect.ValueOf holds the concrete bits. Kind is a coarse enum (struct, ptr, slice, …). Two different named struct types have different Type values but the same Kind (struct).
Type vs Kind, Elem, and struct metadata
Name, Kind, and Elem
Type.Name is empty for anonymous or composite tops (for example the type of []int has no name). For a defined type Foo, Name is Foo and Kind is struct. For pointers, slices, maps, channels, and arrays, Elem() returns the element type.
Struct fields and tags
On a Kind struct, NumField and Field(i) return StructField values with Name, Type, Index, Anonymous, and Tag—what encoding/json reads for json:"...". Unexported fields are visible to reflection in the same package only; from another package you cannot take Interface() on them.
Panics are normal if you skip Kind checks
Many Type and Value methods panic when the Kind is wrong (for example Field on a map). Guard with switch v.Kind() { ... } before deep logic.
package main
import (
"fmt"
"reflect"
)
type Foo struct {
A int `tag1:"First Tag" tag2:"Second Tag"`
B string
}
func dumpType(t reflect.Type, indent string) {
fmt.Printf("%sname=%q kind=%s\n", indent, t.Name(), t.Kind())
switch t.Kind() {
case reflect.Ptr, reflect.Slice, reflect.Map, reflect.Array, reflect.Chan:
dumpType(t.Elem(), indent+" ")
case reflect.Struct:
for i := 0; i < t.NumField(); i++ {
sf := t.Field(i)
fmt.Printf("%sfield %d: %s type=%s kind=%s tag1=%q\n",
indent, i+1, sf.Name, sf.Type, sf.Type.Kind(), sf.Tag.Get("tag1"))
}
}
}
func main() {
var xs []int
dumpType(reflect.TypeOf(xs), "")
dumpType(reflect.TypeOf(Foo{}), "")
dumpType(reflect.TypeOf((*Foo)(nil)), "")
}One local run began like:
name="" kind=slice
name="int" kind=int
name="Foo" kind=struct
field 1: A type=int kind=int tag1="First Tag"
field 2: B type=string kind=string tag1=""
name="" kind=ptr
name="Foo" kind=struct
field 1: A type=int kind=int tag1="First Tag"
...reflect.Value: read, write, New
Read-only vs settable
reflect.ValueOf(x) on a value copy is usually not settable. For a struct field you need reflect.ValueOf(&s).Elem().Field(i) and verify CanSet before Set, SetInt, SetString, etc. Wrong Kind plus SetInt panics.
reflect.New(t) allocates a pointer to zero value of type t and returns a Value holding *T; combine with Elem() to mutate the inner value.
package main
import (
"fmt"
"reflect"
)
type S struct {
A int
B string
}
func main() {
x := S{A: 1, B: "hello"}
v := reflect.ValueOf(&x).Elem()
v.Field(0).SetInt(20)
v.Field(1).SetString("goodbye")
fmt.Println(x)
n := reflect.New(reflect.TypeOf(0)).Elem()
n.SetInt(99)
fmt.Println(n.Interface().(int))
}Expected lines include {20 goodbye} and 99.
Value.String vs fmt for numbers
Value.String: for Kind == String it returns the string; for other kinds it returns a descriptive placeholder, not a decimal rendering of an int. Use Int, Uint, Float, or format v.Interface().
MakeSlice, MakeMap, and MakeChan
Where the language uses make, reflection exposes reflect.MakeSlice, reflect.MakeMap, and reflect.MakeChan. You pass a reflect.Type (often from reflect.TypeOf((*T)(nil)).Elem() or TypeOf(zero) tricks) and receive a Value you can fill and finally .Interface() into a typed variable.
package main
import (
"fmt"
"reflect"
)
func main() {
sl := reflect.MakeSlice(reflect.TypeOf([]int(nil)), 1, 3)
sl.Index(0).SetInt(10)
mp := reflect.MakeMap(reflect.TypeOf(map[string]int(nil)))
mp.SetMapIndex(reflect.ValueOf("hello"), reflect.ValueOf(10))
fmt.Println(sl.Interface(), mp.Interface())
}Expected shape: [10] map[hello:10] (map iteration order not guaranteed elsewhere).
reflect.MakeFunc: generated wrappers
reflect.MakeFunc builds a func value from a reflect.Type whose Kind is Func and a shim func([]reflect.Value) []reflect.Value. Common uses include decorators (timing, tracing), RPC adapters, and small DSLs. The in/out Value slices must match the function type exactly.
package main
import (
"fmt"
"reflect"
"time"
)
func work() {
time.Sleep(100 * time.Millisecond)
fmt.Print("starting")
time.Sleep(50 * time.Millisecond)
fmt.Println("ending")
}
func wrap(fn interface{}) interface{} {
v := reflect.ValueOf(fn)
return reflect.MakeFunc(v.Type(), func(args []reflect.Value) []reflect.Value {
t0 := time.Now()
out := v.Call(args)
fmt.Printf("calling took %s\n", time.Since(t0))
return out
}).Interface()
}
func main() {
timed := wrap(work).(func())
timed()
}You should see startingending then a calling took ... line.
StructOf: anonymous struct types and sharp edges
reflect.StructOf builds a new struct type from a slice of StructField descriptions. The result is an unnamed struct type; you mostly work through reflect.Value after reflect.New. This is niche compared to normal type T struct { ... }, but it appears in dynamic schemas and some serializers.
Limitations worth knowing (also discussed in older public tutorials on golang reflection):
- Types built with
StructOfcannot gain methods, so they cannot implement interfaces at runtime the way a hand-writtentype User struct{}with methods does. - Embedding and delegation interact badly with some reflective constructions; see golang/go#15924 and golang/go#16522 for historical context—treat
StructOfas advanced and test-heavy.
package main
import (
"fmt"
"reflect"
)
func main() {
fields := []reflect.StructField{
{Name: "X", Type: reflect.TypeOf(0), Tag: `json:"x"`},
{Name: "Y", Type: reflect.TypeOf([]int{}), Tag: ""},
}
st := reflect.StructOf(fields)
v := reflect.New(st).Elem()
v.Field(0).SetInt(2)
v.Field(1).Set(reflect.ValueOf([]int{1, 2, 3}))
fmt.Println(v.Interface())
fmt.Println(st.String())
}Value.Call, Method, and MethodByName
Value.Call invokes a func Value. Method and MethodByName are easy to misuse across pointer vs value receivers; many callers use reflect.ValueOf(&x) then Elem() or Addr() deliberately.
package main
import (
"fmt"
"reflect"
)
func Add(a, b int) int { return a + b }
func main() {
v := reflect.ValueOf(Add)
out := v.Call([]reflect.Value{reflect.ValueOf(2), reflect.ValueOf(3)})
fmt.Println(out[0].Int())
}5 is the expected result.
Slices: Index, Set, Len, Cap, Append
Use Len, Cap, Index for elements. Growing slices goes through reflect.Append, then assign back into the original Value if you started from an addressable slice.
package main
import (
"fmt"
"reflect"
)
func main() {
s := []int{1}
v := reflect.ValueOf(&s).Elem()
v.Set(reflect.Append(v, reflect.ValueOf(2)))
fmt.Println(s)
}Expected: [1 2].
DeepEqual, IsZero, and Comparable
reflect.DeepEqual is convenient for tests but a poor default for domain equality: it distinguishes nil and empty slices in ways that surprise newcomers, recurses through interfaces, and ignores your business rules.
Value.IsZero matches the language's "all fields zero" rule for structs and is useful for validation frameworks.
For ordered comparison of arbitrary values in tests, many teams prefer github.com/google/go-cmp/cmp over DeepEqual.
Generics vs golang reflect
Since Go 1.18, generics cover many patterns that older code solved with reflect (containers, parsers with type parameters). Rule of thumb:
- Generics when the type parameter is known at compile time for each instantiation (tree, result wrapper, parser for
T). - Reflection when types are data (JSON into unknown shapes, SQL row scanners, plugin architectures, struct tag-driven routers).
You can combine both: generic APIs with small reflect cores.
Safety checklist before you ship reflection
- Branch on
Kindbefore calling type-specific APIs. - Check
CanSet,CanAddr, andNil()where relevant. - Avoid
MethodByNameon hot paths; it allocates and string-compares. - Remember JSON and gob already embed reflection; sometimes the right move is configuration, not a new reflective layer.
- Prefer compile-time helpers (generics, code generation) when the type set is closed.
Summary
Golang reflection spans reflect.TypeOf, reflect.ValueOf, Kind, struct fields and tags, settable paths through pointers, allocation with New, container construction with MakeSlice / MakeMap / MakeChan, higher-order shims with MakeFunc, anonymous struct types via StructOf, calls through Call, growth with Append, and test utilities like DeepEqual / IsZero. It cannot invent methods or implement interfaces dynamically the way some JVM metaprogramming can. For golang reflection work in production, keep the Laws of Reflection in mind, guard Kinds, and reach for generics first when they express the same design more clearly.
References
- Package reflect
- The Laws of Reflection
- Go spec: Package unsafe (escape hatch, not reflection)
- reflect.Value.CanSet
- reflect.MakeFunc
- reflect.StructOf
- encoding/json source (reflect-based)

