The reflect package provides reflect.TypeOf and reflect.ValueOf to inspect the concrete type and value hidden behind an interface{} at runtime, and can go further to read struct tags, iterate fields, or even set values dynamically. It’s the mechanism behind generic-feeling standard library code like encoding/json and fmt.Println, which need to handle any type without knowing it in advance — but it bypasses compile-time type checking and runs noticeably slower than direct code, so idiomatic Go treats it as a tool of last resort for application code.
TypeOf and ValueOf
reflect.TypeOf returns a value's dynamic type; reflect.ValueOf returns a wrapper you can query and, for addressable values, mutate.
var x float64 = 3.4
t := reflect.TypeOf(x)
v := reflect.ValueOf(x)
fmt.Println(t) // float64
fmt.Println(v.Kind()) // float64
fmt.Println(v.Float()) // 3.4Inspecting struct fields and tags
This is reflection's most common real-world use: walking a struct's fields to read their names, types, and tags — exactly what encoding/json does to know which struct field maps to which JSON key.
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
t := reflect.TypeOf(User{})
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Println(field.Name, field.Tag.Get("json"))
}
// Name name
// Age ageWhy idiomatic Go avoids it when possible
Reflection code loses the compiler's help: a typo in a field name or a wrong type assertion becomes a runtime panic instead of a compile error, and reflective operations run substantially slower than direct field access or a type switch. Since generics arrived in Go 1.18, many use cases that once needed reflection (writing one function that works across several types) can be solved with type parameters instead, which keep compile-time safety. Reflection remains the right tool specifically for generic serialization, ORMs, and dependency-injection frameworks that must handle arbitrary, unknown-in-advance types.