Question
Given this Go struct:
type Example struct {
aNumber uint32
aString string
}
r := &Example{
aNumber: (2 << 31) - 1,
aString: "....",
}
I can iterate over selected field values by manually building a slice:
for _, value := range []interface{}{r.aNumber, r.aString} {
// Use value
}
Is there a more versatile way to create this slice without listing every field individually? Alternatively, what is the proper way to iterate through all fields of a struct using Go's reflect package? For example, after calling reflect.ValueOf(*r).Field(0), what should I do next?
Short Answer
Go does not provide a normal for range operation for struct fields. If you need generic field-by-field processing, use the reflect package. Reflection lets you count fields and retrieve each value at runtime, but it has important limitations, especially for unexported fields. When a collection is meant to be iterated, a slice or map is usually a better data model than a struct.
Concept
A Go struct is a fixed record: each field has a name, type, and position known at compile time.
type User struct {
Name string
Age int
Admin bool
}
Unlike a slice, array, or map, a struct is not a collection that supports range. Its fields can have unrelated types, so this is not valid:
// Invalid: structs cannot be ranged over.
for _, value := range user {
fmt.Println(value)
}
Use reflection when code does not know the exact struct type until runtime. Reflection can inspect a struct's metadata and values, which is useful for generic tools such as serializers, validators, loggers, and configuration loaders.
However, reflection should not be the default solution. If your program's main task is to process a variable number of similar items, model those items as a slice or map. A struct is best when fields have distinct meanings, such as Name, Age, and Email.
Mental Model
Think of a struct as a paper form with labeled boxes:
Nameis one labeled box.Ageis another labeled box.- Each box can hold a different kind of value.
Normally, Go expects you to refer to a box by its label: user.Name.
Reflection is like examining the form with a scanner. The scanner can discover how many boxes exist, read their labels, and inspect their contents. This is flexible, but less direct and less type-safe than using the labels yourself.
A slice is different: it is a row of similar boxes numbered 0, 1, 2, and so on. That is why slices naturally support iteration.
Syntax and Examples
To inspect a struct through a pointer, first get the value the pointer refers to with Elem().
package main
import (
"fmt"
"reflect"
)
type Example struct {
Number uint32
Text string
}
func main() {
r := &Example{
Number: (2 << 31) - 1,
Text: "Hello",
}
value := reflect.ValueOf(r).Elem()
typ := value.Type()
for i := 0; i < value.NumField(); i++ {
fieldInfo := typ.Field(i)
fieldValue := value.Field(i)
fmt.Printf("%s = %v\n", fieldInfo.Name, fieldValue.Interface())
}
}
Output:
Number = 2147483647
Text = Hello
Important pieces:
reflect.ValueOf(r)creates a reflection value forr, which is a*Examplepointer..Elem()follows that pointer and returns the underlyingExamplestruct value.NumField()returns the number of declared struct fields.Field(i)retrieves the value at positioni.Type().Field(i)retrieves metadata, including the field name and struct tags.Interface()converts the reflected value toany(formerly written asinterface{}).
The example uses exported fields (Number, Text). A field name starting with an uppercase letter is exported in Go.
Step by Step Execution
Consider this code:
person := &Person{Name: "Mina", Age: 28}
value := reflect.ValueOf(person).Elem()
typ := value.Type()
for i := 0; i < value.NumField(); i++ {
fmt.Println(typ.Field(i).Name, value.Field(i).Interface())
}
Assume the type is:
type Person struct {
Name string
Age int
}
Execution proceeds as follows:
personholds a pointer to aPersonvalue.reflect.ValueOf(person)represents the pointer, not the struct itself..Elem()follows the pointer, producing a reflectedPersonstruct.value.NumField()returns2becausePersonhasNameandAge.- On the first loop iteration,
iis0:typ.Field(0).Nameis"Name".value.Field(0).Interface()is the string"Mina".
- On the second iteration,
iis1:typ.Field(1).Nameis"Age".value.Field(1).Interface()is the integer28.
- The loop ends after index
1.
The output is:
Name Mina
Age 28
Real World Use Cases
Reflection-based struct iteration is useful when the same logic must work for many different struct types.
- Validation: Inspect fields with tags such as
validate:"required"and reject missing values. - JSON or database mapping: Libraries inspect field names and tags to decide which data to encode or decode.
- Logging and debugging: Print a record's field names and values without writing custom logging for every type.
- Configuration loading: Match environment variables or configuration keys to struct fields.
- Command-line parsing: Populate fields from command-line flags based on tags.
For application-specific logic, direct field access is clearer:
if user.Name == "" {
return errors.New("name is required")
}
Reflection is more appropriate when writing reusable infrastructure that must support many structs.
Real Codebase Usage
In real Go projects, reflection is commonly isolated in a small utility, library, or framework boundary. Business logic usually remains explicit and type-safe.
Check the kind before treating a value as a struct
A reusable helper should avoid panics when called with the wrong value:
func PrintFields(input any) {
value := reflect.ValueOf(input)
if value.Kind() == reflect.Ptr {
if value.IsNil() {
return
}
value = value.Elem()
}
if value.Kind() != reflect.Struct {
return
}
typ := value.Type()
for i := 0; i < value.NumField(); i++ {
field := value.Field(i)
if !field.CanInterface() {
continue
}
fmt.Printf("%s=%v\n", typ.Field(i).Name, field.Interface())
}
}
This uses guard clauses to handle pointers, nil pointers, and non-struct inputs early.
Use tags to control generic behavior
Real code often reads tags while iterating:
type Account struct {
ID int `json:"id"`
Password string `json:"-"`
}
A serializer can inspect the json tag and skip Password. Most projects use established packages such as encoding/json rather than writing this reflection logic themselves.
Prefer a slice for repeated data
If fields are logically a list, store them as one:
type Report struct {
Scores []int
}
for _, score := range report.Scores {
fmt.Println(score)
}
This avoids reflection and makes additions or removals of items straightforward.
Common Mistakes
Trying to range over a struct
// Does not compile.
for _, value := range example {
fmt.Println(value)
}
Only arrays, slices, strings, maps, and channels support range. Use direct access, reflection, or a different data structure.
Forgetting Elem() for a pointer
value := reflect.ValueOf(r)
fmt.Println(value.NumField()) // Panic: value is a pointer, not a struct
Use Elem() after confirming the value is a non-nil pointer:
value := reflect.ValueOf(r).Elem()
Calling Interface() on an unexported field
type Example struct {
number uint32
}
value := reflect.ValueOf(Example{number: 7})
fmt.Println(value.Field(0).Interface()) // Panic
Reflection protects unexported fields. Before calling Interface(), check CanInterface():
field := value.Field(0)
if field.CanInterface() {
fmt.Println(field.Interface())
}
For generic reflection code, prefer exported fields. Do not use unsafe merely to bypass visibility rules.
Assuming every value has the same type
field.Interface() returns any, so type-specific operations require a type assertion or type switch:
switch v := field.Interface().(type) {
case string:
fmt.Println("string length:", len(v))
case int:
fmt.Println("integer:", v)
}
Using reflection when a slice is the right model
A struct with fields such as Item1, Item2, and Item3 is difficult to extend and iterate. Prefer:
type Order struct {
Items []string
}
Then use a normal range loop.
Comparisons
| Approach | Best for | Advantages | Limitations |
|---|---|---|---|
| Direct field access | Known fields in normal application code | Type-safe, clear, fast | Must write each field explicitly |
| Slice or array | A sequence of similar values | Natural range, easy to grow with slices | Elements normally share one type |
| Map | Values looked up by dynamic keys | Natural range, flexible keys | Less structure; map iteration order is not guaranteed |
| Reflection | Generic tools that accept many struct types | Can inspect fields and tags at runtime | More complex, slower, can panic if misused |
A struct with an any slice is possible:
values := []any{r.Number, r.Text}
But this loses static type information. It is useful only when mixed values genuinely need uniform handling.
For fixed fields with different meanings, keep a struct. For repeatable values, choose a slice. For reusable metadata-driven tooling, use reflection.
Cheat Sheet
value := reflect.ValueOf(input)
- Gets a reflected value.
value = value.Elem()
- Follows a pointer to its target.
- Call only when
value.Kind() == reflect.Ptrand!value.IsNil().
if value.Kind() != reflect.Struct {
return
}
- Ensure the target is a struct before using struct operations.
typ := value.Type()
for i := 0; i < value.NumField(); i++ {
info := typ.Field(i)
field := value.Field(i)
if field.CanInterface() {
fmt.Println(info.Name, field.Interface())
}
}
Quick rules:
- Structs cannot be iterated with
range. NumFieldandFieldrequire a reflected struct value.Type().Field(i)gives field metadata;Value.Field(i)gives its value.Interface()can panic for unexported fields; useCanInterface().- Use exported field names for generic reflection-based access.
- Prefer slices or maps when iteration is central to the data model.
FAQ
Can you use range on a struct in Go?
No. Go's range works with arrays, slices, strings, maps, and channels, but not structs.
How do I iterate over fields of a pointer to a struct?
Use reflect.ValueOf(pointer).Elem() to obtain the pointed-to struct, then loop from 0 to NumField() - 1.
Why does reflect.Value.Interface() panic for a field?
The field is often unexported. Check field.CanInterface() first, or design generic code around exported fields.
What is the difference between Value.Field(i) and Type.Field(i)?
Value.Field(i) returns the field's runtime value. Type.Field(i) returns metadata, such as its name, type, and tags.
Should I use reflection to process ordinary structs?
Usually no. Direct field access is clearer and safer when the struct type is known. Reflection is best for generic infrastructure.
How can I modify a struct field with reflection?
Start with a pointer, call Elem(), then ensure the field's CanSet() method returns true. The new value must have an assignable type.
Is reflection slower than direct field access?
Yes, reflection generally has additional runtime work and less compile-time checking. Use it when its flexibility is needed, not for routine field access.
Mini Project
Description
Build a small struct inspection utility for debug output. It accepts a struct or a pointer to a struct, prints each exported field name and value, and safely rejects unsupported inputs. This demonstrates the checks production reflection helpers need before accessing fields.
Goal
Create a function that prints the exported fields of any non-nil struct value or pointer to a struct.
Requirements
Accept both a struct value and a pointer to a struct.
Return a useful error for nil pointers and non-struct values.
Print each exported field as Name: value.
Skip fields that cannot safely be converted with Interface().
Use reflect rather than hard-coding a particular struct type.
Keep learning
Related questions
Are Go Maps Passed by Value or Reference?
Learn Go map parameter and return semantics: maps are passed by value, while copies share underlying data. Know when map pointers are useful.
Authentication for Go REST APIs: Sessions, Tokens, and Middleware
Learn practical authentication patterns for Go REST APIs, including password hashing, server sessions, bearer tokens, middleware, and security rules.
Automatic Build Versioning in Go: Embed Incrementing Build Numbers
Learn how to add automatic build versioning in Go using linker flags, build metadata, CI counters, and Git-based version values.