What is reflection in Go?

Reflection in Go refers to a program’s ability to observe its own variables and values during runtime. It enables us to inspect an object’s type, call its methods, and change its fields at runtime, even if the types are unknown at compile-time. In Go, reflection is implemented using the reflect package.

Reflection is useful for multiple reasons:

  • Dynamic typing: Go is a statically typed languageA statically typed language is one in which variable types are determined at compile-time and must be explicitly declared., which means that types are determined during compilation. However, reflection enables us to interact with types dynamically at runtime.

  • Generic programming: Although Go now supports genericsGenerics allow for writing flexible and reusable code that can operate on various data types while maintaining type safety. as of version 1.18, reflection builds generic algorithms by dynamically manipulating types.

  • Introspection: Reflection allows a program to analyze itself, which is crucial for debugging, serialization, and developing flexible and generic APIs.

  • Metaprogramming: Reflection allows programs to change their structure and behavior, enabling advanced metaprogramming techniques.

Internals of the reflect package

The reflect package in Go includes methods and types for runtime reflection. It gives a set of types (Type, Value, Kind, etc.) and runtime functions for examining and modifying types and values. The reflect package internally uses the runtime reflection features the Go runtime offers. The reflect package obtains information about the interface’s type and value, such as methods, fields, and other metadata, through interaction with the runtime when we call functions such as TypeOf or ValueOf.

Understanding the reflect package in Go
Understanding the reflect package in Go

Type vs. kind 

In Go, type and kind are associated with two different concepts:

Type

Kind

It refers to the specific concrete type of a value.

It is a broader classification of types in reflection.

Examples include int, string, struct, interface, etc.

It categorizes types into high-level groups (int, string, struct, interface, slice, map, etc.).

Types in reflection are represented by the reflect.Type interface.

It utilizes the Kind() method provided by reflect.Value to obtain the kind of a value.

Methods like TypeOf can be used to obtain the reflection type of a value.

For example, both int and int64 have a kind of reflect.Int, indicating they are integers, despite being distinct types.

Examples

Here are some frequent examples that demonstrate various reflection capabilities:

  • Examining types: We can use reflection to determine a variable’s type, examine if it implements specific interfaces, and obtain information about its methods and fields.

package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.14
fmt.Println("Type:", reflect.TypeOf(x))
fmt.Println("Value:", reflect.ValueOf(x))
}
  • Modifying values: Reflection enables us to change the values of variables even when their types are unknown at compile-time.

package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.14
v := reflect.ValueOf(&x)
fmt.Println("Old value of x:", x)
v.Elem().SetFloat(6.28)
fmt.Println("New value of x:", x)
}
  • Creating instances: Reflection can be used to create instances of types dynamically.

package main
import (
"fmt"
"reflect"
)
func main() {
t := reflect.TypeOf(3)
v := reflect.New(t).Elem()
fmt.Println("Old value: ", v.Interface())
v.SetInt(10)
fmt.Println("New value: ", v.Interface())
}

Note: These examples show only a portion of what reflection can do. It’s a powerful feature, but it should be utilized carefully because of the potential for complexity and performance challenges.

Coding example

Here's an example of Go reflection that dynamically creates and modifies struct values by user input. This code uses reflection to dynamically create a Person struct and populate its fields with provided data. It demonstrates how reflection can be used to analyze and edit struct fields dynamically during runtime:

package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
Age int
IsAdult bool
}
func main() {
// Sample input data
data := map[string]interface{}{
"Name": "Alice",
"Age": 30,
"IsAdult": true,
}
// Creating a new instance of the Person struct
personType := reflect.TypeOf(Person{})
newPerson := reflect.New(personType).Elem()
// Populating the struct fields dynamically using reflection
for fieldName, fieldValue := range data {
field := newPerson.FieldByName(fieldName)
if !field.IsValid() {
continue // Skip invalid fields
}
if field.CanSet() {
value := reflect.ValueOf(fieldValue)
if value.Type().AssignableTo(field.Type()) {
field.Set(value)
} else {
fmt.Printf("Type mismatch for field '%s'\n", fieldName)
}
} else {
fmt.Printf("Cannot set field '%s'\n", fieldName)
}
}
// Getting the populated Person struct
createdPerson := newPerson.Interface().(Person)
fmt.Println("Created Person:", createdPerson)
}

Code explanation

The explanation of the above code is as follows:

  • Lines 3–6: Imports the fmt package for formatting and printing and the reflect package for performing reflection operations.

  • Lines 8–12: Declares a Person struct with three fields: Name, Age, and IsAdult.

  • Lines 16–20: Initializes a data map containing sample values for the Person struct fields.

  • Line 23: Uses reflect.TypeOf to obtain the type information of the Person struct.

  • Line 24: Creates a new zero-initialized value of the Person type using reflect.New.

  • Lines 27–43: The loop in data iterates, fetching, and validating struct fields by name. It determines whether fields can be modified and, upon type compatibility, updates field values using reflection.

Limitations

Here are a few limitations of reflection in Go:

  • Performance overhead: Reflection in Go can incur a significant performance overhead compared to static type checking. This is because runtime-type information needs to be accessed and processed dynamically.

  • Lack of type safety: Reflection avoids the compiler's type verification by operating on interface {} types. This can result in runtime errors if the types are not used correctly.

  • Inability to create new methods dynamically: Go does not support the creation of new methods during runtime. Methods are statically declared at compilation time, and there is no way to add or change them dynamically.

  • Inability to implement interfaces dynamically: Unlike other languages, like JavaScript or Python, Go does not allow dynamic interface implementation at runtime. Interfaces are statically defined, and types must explicitly declare their intended interface implementation.

Conclusion

Reflection in Go, enabled by the reflect package, allows developers to analyze and modify types and values at runtime. While it provides flexibility, reflection involves performance overhead and skips compile-time type check, which might lead to runtime errors if misused. Notably, Go restricts dynamically defining methods or implementing interfaces, which limits the scope of reflection. Thus, while reflection remains a useful tool for runtime observation and manipulation, it should be used with static typing and interfaces to ensure code clarity and performance efficiency.

Free Resources

Copyright ©2025 Educative, Inc. All rights reserved