Look, you’ve met interface{} (or its less shy alias, any). It’s the empty party that lets any type in. The real fun starts when you need to get a specific guest out of that party. You have two main tools for this: type assertions and the reflect package. One is a precise, lightweight scalpel; the other is a full surgical theater with a power plant attached. Knowing which to grab is the mark of a Go developer who doesn’t hate their CPU—or themselves.

The Scalpel: Type Assertions

A type assertion is your first, best choice. It’s a single, deliberate operation that checks if a value held by an interface is of a specific concrete type. It’s brutally efficient because it’s baked directly into the language’s runtime. Under the hood, it’s checking a type pointer. It’s about as fast as these things get.

You use it in two ways. The safe way, which tells you if it worked, and the “I’m-feeling-lucky” way, which panics if you’re wrong.

var myValue any = "I am a string"

// The safe way: check the 'ok'
str, ok := myValue.(string)
if ok {
    fmt.Printf("Success! Value is: %s\n", str) // Prints: Success! Value is: I am a string
} else {
    fmt.Println("Value was not a string")
}

// The "I'm-feeling-lucky" way: panic on failure
str := myValue.(string) // Fine
num := myValue.(int)    // Panic: interface conversion: interface {} is string, not int

The rule is simple: Use the safe, two-value assignment unless you have absolute, 100%, iron-clad certainty of the type. That certainty almost only exists in the context of a type switch (which we’ll get to) or a tightly controlled package. If you’re getting data from the outside world (JSON, user input, a network call), you must use the safe form. A panic is not a control flow mechanism.

The Surgical Theater: The Reflect Package

Reflection is what you use when the scalpel isn’t enough. It’s for when you don’t just need to know if it’s a string, but you need to know if it’s a struct, how many fields it has, what their names and types are, and then maybe call a method by its string name. It’s for deep, dynamic, and often messy introspection.

import "reflect"

func investigate(v any) {
    t := reflect.TypeOf(v)
    fmt.Printf("Kind: %v, Name: %v\n", t.Kind(), t.Name())

    if t.Kind() == reflect.Struct {
        fmt.Println("It's a struct! Fields:")
        for i := 0; i < t.NumField(); i++ {
            field := t.Field(i)
            fmt.Printf("  %s: %v\n", field.Name, field.Type)
        }
    }
}

investigate("a string") // Kind: string, Name: string
investigate(struct{ Name string }{}) // Kind: struct, Name: ... Fields: Name: string

Reflection is incredibly powerful, but it’s also slow, verbose, and turns compile-time errors into runtime errors. The compiler can’t help you when you’re passing around reflect.Value objects.

So, Which Tool Do I Grab?

Here’s the simple heuristic: If you can do it with a type assertion or a type switch, you should. Full stop. The performance difference is not trivial; it’s monumental. Reflection should be your last resort, reserved for problems that are inherently about unknown types, not many known types.

  • Use a type assertion when you have a strong expectation of one or two possible types. Checking for an error or extracting a string from a known map of any values are perfect examples.
  • Use a type switch when you have a closed set of several possible concrete types you need to handle. It’s a clean, efficient way to branch your logic.
func handleType(x any) {
    switch x.(type) {
    case string:
        fmt.Println("It's a string!")
    case int:
        fmt.Println("It's an int!")
    case nil:
        fmt.Println("It's nil! Why would you do that?")
    default:
        fmt.Printf("No clue, boss. Type is %T\n", x)
    }
}
  • Use reflection only when the problem is fundamentally about the structure itself. Writing a generic JSON/YAML/TOML marshaller? That’s a job for reflect. Building a framework that automatically maps HTTP form data to arbitrary user-provided structs? Yep, that’s reflect. Needing to check if something is a string? For the love of performance, use a type assertion.

The reflect package is the tool you break out when the problem is so complex that the verbosity and performance hit are worth the payoff. For 95% of your daily work with interfaces, the type assertion is your best friend. It’s direct, fast, and clearly communicates your intent.