Reflection with the reflect Package

Reviewed & published by Brayan K

Reflection lets a program inspect and modify values whose types it doesn't know at compile time. You'll use reflect.TypeOf and ValueOf, tell Kind from Type, read struct tags the way encoding/json does, set fields via Elem and CanSet, call methods dynamically — and learn the three laws and when not to use it.

Part of the free Go course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

1️⃣ TypeOf, ValueOf, and Kind

reflect.TypeOf gives a reflect.Type; reflect.ValueOf gives a reflect.Value. The Kind is the broad category (int, slice, struct), distinct from the specific named Type.

package main

import (
    "fmt"
    "reflect"
)

func describe(x any) {
    t := reflect.TypeOf(x)  // the dynamic Type
    v := reflect.ValueOf(x) // the dynamic Value

    // Kind is the broad CATEGORY (int, string, slice, struct...);
    // Type is the specific named type.
    fmt.Printf("type=%v kind=%v value=%v\n", t, t.Kind(), v)
}

func main() {
    describe(42)            // type=int kind=int value=42
    describe("hi")          // type=string kind=string value=hi
    describe([]int{1, 2})   // type=[]int kind=slice value=[1 2]
}

2️⃣ Inspecting struct fields and tags

Loop over a struct's fields with NumField and Field(i), then read tags with f.Tag.Get("json"). This is precisely how encoding/json maps fields to JSON keys.

package main

import (
    "fmt"
    "reflect"
)

type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email,omitempty"`
}

func main() {
    t := reflect.TypeOf(User{})

    // Walk the struct's fields and read their json tags —
    // this is exactly how encoding/json maps fields to keys.
    for i := 0; i < t.NumField(); i++ {
        f := t.Field(i)                 // a reflect.StructField
        tag := f.Tag.Get("json")        // read the json tag
        fmt.Printf("%-6s -> json:%q\n", f.Name, tag)
    }
}

3️⃣ Settability: Elem and CanSet

reflect.ValueOf(x) is a copy and not settable. To modify the original, pass a pointer and call Elem() to reach the addressable value — then CanSet() is true.

package main

import (
    "fmt"
    "reflect"
)

func main() {
    x := 10

    // ValueOf(x) is a COPY and not settable.
    v := reflect.ValueOf(x)
    fmt.Println("CanSet (value):", v.CanSet()) // false

    // To modify x, pass a POINTER and use Elem() to reach the
    // addressable value it points to.
    p := reflect.ValueOf(&x)
    elem := p.Elem()
    fmt.Println("CanSet (elem): ", elem.CanSet()) // true

    elem.SetInt(99) // safe now: addressable + settable
    fmt.Println("x is now:", x) // 99
}

4️⃣ Calling methods dynamically

Value.MethodByName looks up a method at runtime, and Call invokes it with a []reflect.Value of arguments, returning the results the same way.

package main

import (
    "fmt"
    "reflect"
)

type Greeter struct{ Name string }

func (g Greeter) Greet(greeting string) string {
    return greeting + ", " + g.Name + "!"
}

func main() {
    g := Greeter{Name: "Ada"}
    v := reflect.ValueOf(g)

    // Look up the method by name and call it dynamically.
    m := v.MethodByName("Greet")
    args := []reflect.Value{reflect.ValueOf("Hello")}
    out := m.Call(args) // returns a []reflect.Value

    fmt.Println(out[0].String()) // Hello, Ada!
}

🎯 Your Turn

Inspect a struct. Fill in the two blanks marked ___ to count fields and read the first field's json tag.

package main

import (
    "fmt"
    "reflect"
)

type Point struct {
    X int `json:"x"`
    Y int `json:"y"`
}

func main() {
    t := reflect.TypeOf(Point{})

    // 🎯 YOUR TURN — fill in the blanks marked ___

    // 1) How many fields does the struct have?
    n := t.___()              // 👉 NumField   -> 2
    fmt.Println("fields:", n)

    // 2) Read the json tag of the first field
    f := t.Field(0)
    fmt.Println(f.Name, f.Tag.___("json")) // 👉 Get  -> X x

    // ✅ Run it free at the Go Playground.
}

Common Errors (and the fix)

❌ Calling SetX on reflect.ValueOf(x) — panics (not settable).

✅ Pass &x and use .Elem() to get a settable Value.

❌ Reflecting on unexported fields and trying to set them — panics.

✅ Only set exported (capitalized) fields; check CanSet().

❌ Confusing Kind with Type — a named int has Kind Int.

✅ Use Kind() for category, Type for the exact type.

❌ Reaching for reflection by default — slow and unsafe.

✅ Prefer interfaces or generics; use reflection only when truly dynamic.

🧠 Quick Recall

how to make a Value settable?

Pass a pointer and call Elem(). Then it is addressable and CanSet() is true.

who reads json struct tags?

encoding/json via reflection — field.Tag.Get("json") maps a Go field to its JSON key.

🎯 Mini-Challenge: Set Fields by Name

Take a *Config, get a settable value with Elem(), and set Host and Port by name with FieldByName.

package main

import (
    "fmt"
    "reflect"
)

type Config struct {
    Host string
    Port int
}

// 🎯 MINI-CHALLENGE: set struct fields by name via a pointer.
// 1. Take a *Config and reflect.ValueOf(p).Elem() to get a settable value.
// 2. Use FieldByName("Host").SetString("localhost").
// 3. Use FieldByName("Port").SetInt(8080).
// 4. Print the struct: should be {localhost 8080}.
//
// ✅ Hint: you MUST pass a pointer and call Elem() so CanSet() is true.

func main() {
    cfg := &Config{}
    _ = reflect.ValueOf(cfg).Elem()
    fmt.Println(*cfg)
}

🎉 Lesson Complete!

Practice quiz

What does reflect.TypeOf(x) return?

  • a reflect.Type describing x's dynamic type
  • the string name only
  • a reflect.Value
  • the size of x in bytes

Answer: a reflect.Type describing x's dynamic type. reflect.TypeOf returns a reflect.Type; reflect.ValueOf returns a reflect.Value holding the runtime value.

What is the difference between a Type's Kind and its Type?

  • Type is always 'interface'
  • they are always identical
  • Kind is the underlying category (struct, int, slice...); Type is the specific named type
  • Kind is the package name

Answer: Kind is the underlying category (struct, int, slice...); Type is the specific named type. Kind is the broad category like reflect.Struct or reflect.Int; the Type can be a specific named type such as time.Duration whose Kind is Int64.

How do you read a struct field's tag with reflect?

  • v.Tag()
  • field.Tag.Get("json") on the StructField
  • reflect.Tag(v)
  • v.Field(0).Tag

Answer: field.Tag.Get("json") on the StructField. t.Field(i) returns a reflect.StructField whose Tag.Get("json") reads the json tag value, exactly how encoding/json finds keys.

What does the Elem() method do on a pointer's reflect.Value?

  • deletes the value
  • returns the element type's name only
  • returns the pointer's address
  • returns the Value that the pointer points to

Answer: returns the Value that the pointer points to. For a pointer Value, Elem() dereferences it to the pointed-to Value; on a Type it gives the element/base type.

For CanSet() to be true, the value must be...

  • addressable and exported, typically reached via a pointer's Elem()
  • of kind Int only
  • a constant
  • obtained from reflect.ValueOf(x) directly

Answer: addressable and exported, typically reached via a pointer's Elem(). You can only set addressable, exported fields; pass a pointer and use Elem() so the Value is settable (CanSet() == true).

Which package famously uses struct tags via reflection?

  • os
  • encoding/json
  • sort
  • fmt

Answer: encoding/json. encoding/json reads the json struct tags through reflection to map Go fields to JSON keys.

How do you call a method dynamically by name?

  • reflect.Call(v, name)
  • v.Invoke(name)
  • v.Method(name)()
  • v.MethodByName("Greet").Call(args)

Answer: v.MethodByName("Greet").Call(args). Value.MethodByName returns a callable Value; Call takes a []reflect.Value of arguments and returns the results.

Which is one of the 'three laws of reflection'?

  • Reflection only works on pointers
  • Reflection disables the garbage collector
  • Reflection goes from interface value to reflection object and back
  • Reflection always copies the value

Answer: Reflection goes from interface value to reflection object and back. The laws are: reflection goes from interface to reflection object; from reflection object back to interface; and a Value is settable only if it is addressable/exported.

Why should reflection usually be a LAST resort?

  • it is not in the standard library
  • it bypasses compile-time type checks and is slower and harder to read
  • it only works in tests
  • it cannot read structs

Answer: it bypasses compile-time type checks and is slower and harder to read. Reflection sidesteps static typing, costs performance, and can panic at runtime; prefer interfaces or generics when they fit.

What happens if you call Value.SetInt on a non-settable Value?

  • it panics at runtime
  • it converts the value to a string
  • it silently does nothing
  • it returns an error

Answer: it panics at runtime. Setting a non-addressable/non-settable Value panics; always check CanSet() first or obtain the Value via a pointer's Elem().

Continue this course

Frequently asked questions

What are reflect.TypeOf and reflect.ValueOf?

They are the entry points into reflection. reflect.TypeOf(x) returns a reflect.Type that describes x's dynamic type (its name, kind, fields, methods). reflect.ValueOf(x) returns a reflect.Value that wraps the actual runtime value so you can read it, and — if it is addressable — modify it. Together they let you inspect and manipulate values whose concrete type you don't know at compile time.

What's the difference between Kind and Type?

Kind is the broad category from a fixed set: reflect.Int, reflect.String, reflect.Slice, reflect.Struct, reflect.Ptr, and so on. Type is the specific, possibly named type. For example, time.Duration has Type 'time.Duration' but Kind reflect.Int64. You branch on Kind when you care about the structure (is it a struct? a slice?) and on Type when you care about the exact type.

How does encoding/json use struct tags through reflection?

When you marshal a struct, encoding/json uses reflection to walk its fields with t.Field(i), reads each field's tag with field.Tag.Get("json"), and uses that to decide the JSON key (and options like omitempty). It also skips unexported fields. You can do the same in your own code to build generic serializers, validators, or ORMs driven by tags.

Why do I need a pointer and Elem() to change a value?

reflect.ValueOf(x) copies x, so the Value is not addressable and CanSet() is false — setting it would panic. To mutate the original you pass a pointer, then call Elem() to dereference it to the addressable value it points to. That Value is settable, so SetInt, SetString, or FieldByName(...).Set... will actually change the underlying variable. Always check CanSet() if you're unsure.

Can I call methods dynamically with reflection?

Yes. Value.MethodByName("Greet") returns a callable reflect.Value, and Call(args) invokes it, where args is a []reflect.Value built from reflect.ValueOf for each argument. It returns the results as a []reflect.Value. This powers frameworks that dispatch by name (RPC, command routers), but it bypasses compile-time checks, so a wrong name or argument count panics at runtime.

When should I avoid reflection?

Most of the time. Reflection bypasses the type system, is significantly slower than direct code, and can panic at runtime, so it hurts readability and safety. Prefer ordinary interfaces or, since Go 1.18, generics — they solve many problems that once needed reflection while staying type-safe and fast. Reserve reflection for genuinely dynamic work: serialization, generic data mapping, or tools that must inspect arbitrary types.