Alright, let’s get our hands dirty with reflect.Kind. This is where we move from the philosophical “what is a type?” to the practical “what the heck am I looking at right now?”.

Think of a type’s Kind as its fundamental category. It’s the answer to the question, “Is this a struct? A slice? A pointer to a string? An obscure 16-bit integer?” While a reflect.Type describes the full blueprint (e.g., map[string][]*MyStruct), the Kind tells you the core building material (e.g., reflect.Map). This distinction is crucial because most of your reflection code will branch based on Kind.

You get the Kind of a reflect.Value easily:

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var mySlice []int
	sliceValue := reflect.ValueOf(mySlice)
	fmt.Println("Kind of mySlice:", sliceValue.Kind()) // Output: Kind of mySlice: slice

	var num int
	fmt.Println("Kind of num:", reflect.ValueOf(num).Kind()) // Output: Kind of num: int
}

Seems straightforward, right? Welcome to the first pitfall. Let’s turn that mySlice into a nil slice.

var myNilSlice []int
nilSliceValue := reflect.ValueOf(myNilSlice)
fmt.Println("Kind of myNilSlice:", nilSliceValue.Kind())    // Output: Kind of myNilSlice: slice
fmt.Println("Is it nil?", myNilSlice == nil)                // Output: Is it nil? true
fmt.Println("Is the Value itself nil?", nilSliceValue.IsValid()) // Output: Is the Value itself nil? true

Notice that? The Kind is still reflect.Slice, even though the slice itself is nil. This is a critical insight: a nil slice and an empty but non-nil slice are still slices. Their Kind doesn’t change. The Kind describes the container, not its contents. Checking for nil is a separate operation (value.IsNil()), which only works for kinds that can be nil (slices, maps, pointers, channels, functions, and interfaces). Calling IsNil() on a reflect.Int will panic. Because, well, the number 42 isn’t nil; that’s absurd. The reflection package rightly panics at your absurdity.

The Pointer Conundrum (and How to Solve It)

Here’s where designers made a choice you’ll either call pragmatic or questionable. I call it a necessary evil. When you have a pointer, its Kind is reflect.Ptr. Full stop. The reflect.Type (from value.Type() or reflect.TypeOf()) can tell you what it points to, but the Value itself just knows it’s a pointer.

To do anything useful, you usually need to look at what the pointer points to. For that, you use value.Elem(). This is called “dereferencing” the pointer, and it gives you a reflect.Value representing the pointed-to value. If the pointer is nil, calling Elem() on it will give you a zero Value (one where IsValid() returns false). Let’s see it in action.

func examinePointer(i interface{}) {
	v := reflect.ValueOf(i)
	fmt.Printf("Original Kind: %v\n", v.Kind())

	if v.Kind() == reflect.Ptr {
		fmt.Println("It's a pointer! Let's see what it points to...")
		dereferencedValue := v.Elem()
		if !dereferencedValue.IsValid() {
			fmt.Println("It's a nil pointer. Nothing to see here.")
			return
		}
		fmt.Printf("Dereferenced Kind: %v\n", dereferencedValue.Kind())
		fmt.Printf("Dereferenced Value: %v\n", dereferencedValue.Interface())
	}
}

func main() {
	str := "hello"
	examinePointer(&str)
	// Output:
	// Original Kind: ptr
	// It's a pointer! Let's see what it points to...
	// Dereferenced Kind: string
	// Dereferenced Value: hello

	var nilPtr *string
	examinePointer(nilPtr)
	// Output:
	// Original Kind: ptr
	// It's a pointer! Let's see what it points to...
	// It's a nil pointer. Nothing to see here.
}

Slices, Arrays, and Maps: The Usual Suspects

These collection types are the bread and butter of reflection. Their Kinds are distinct: reflect.Slice, reflect.Array, and reflect.Map. Remember, an array’s size is part of its reflect.Type, not its Kind. A [3]string and a [100]string are both reflect.Array; you’d need to interrogate the Type (value.Type().Len()) to tell them apart.

A common, face-palming mistake is trying to use value.Len() or value.MapKeys() on a nil map or slice. The Kind is correct, but the operation will panic.

var nilMap map[string]int
mapValue := reflect.ValueOf(nilMap)
fmt.Println("Kind:", mapValue.Kind()) // Kind: map

// This will PANIC: call of reflect.Value.MapKeys on zero Value.
// fmt.Println("Number of keys:", mapValue.MapKeys().Len())

// The correct way: check if it's nil first.
if !mapValue.IsNil() {
    keys := mapValue.MapKeys()
    fmt.Println("Number of keys:", keys.Len())
} else {
    fmt.Println("The map is nil, so it has zero keys.")
}

Structs: The Deep End

When you hit a reflect.Struct, you’ve hit the jackpot of complexity. The Kind tells you it’s a struct, but to get the fields, you need to go back to the Type object (value.Type()). You can iterate over the number of fields (value.NumField()) and get a StructField descriptor for each one, which contains its name, type, tags, and more.

type Person struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

func examineStruct(s interface{}) {
	v := reflect.ValueOf(s)
	if v.Kind() == reflect.Struct {
		t := v.Type() // Get the Type to access field metadata
		fmt.Printf("Number of fields: %d\n", v.NumField())
		for i := 0; i < v.NumField(); i++ {
			fieldValue := v.Field(i)
			fieldType := t.Field(i) // reflect.StructField
			fmt.Printf("Field %d: Name=%s, Kind=%v, Value=%v, Tag=%s\n",
				i,
				fieldType.Name,
				fieldValue.Kind(),
				fieldValue.Interface(),
				fieldType.Tag.Get("json"),
			)
		}
	}
}

func main() {
	p := Person{"Alice", 30}
	examineStruct(p)
}
// Output:
// Number of fields: 2
// Field 0: Name=Name, Kind=string, Value=Alice, Tag=name
// Field 1: Name=Age, Kind=int, Value=30, Tag=age

The golden rule? Always check the Kind first. Before you call .Int(), check if it’s reflect.Int. Before you call .Elem(), check if it’s reflect.Ptr. Before you call .MapKeys(), check if it’s reflect.Map and not nil. It’s the defensive programming mantra applied to meta-programming. It turns runtime panics into manageable errors. And it’s what separates you from the poor souls who just copy-pasted a snippet from Stack Overflow without understanding why it explodes on Tuesdays.