Type Assertions and Reflection Boundaries

Updated

July 30, 2026

Overview

Type assertions extract concrete types from interfaces. Reflection provides runtime type inspection but should be used sparingly.

Type Assertions

var i interface{} = "hello"

s := i.(string)        // Panic if wrong type
s, ok := i.(string)    // Safe: ok is false if wrong type

if s, ok := i.(string); ok {
    fmt.Println(s)
}

Type Switches

func describe(i interface{}) {
    switch v := i.(type) {
    case int:
        fmt.Println("int:", v*2)
    case string:
        fmt.Println("string:", len(v))
    case bool:
        fmt.Println("bool:", !v)
    default:
        fmt.Printf("unknown: %T\n", v)
    }
}

The reflect Package

import "reflect"

v := 42
t := reflect.TypeOf(v)   // int
val := reflect.ValueOf(v)  // 42

fmt.Println(t.Name())      // "int"
fmt.Println(t.Kind())      // int
fmt.Println(val.Int())     // 42

Struct Reflection

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

u := User{Name: "Alice", Age: 30}
t := reflect.TypeOf(u)

for i := 0; i < t.NumField(); i++ {
    field := t.Field(i)
    fmt.Printf("%s: %s\n", field.Name, field.Tag.Get("json"))
}

When to Use Reflection

Good uses: - Serialization (JSON, XML) - ORM field mapping - Generic testing utilities - Dependency injection

Avoid for: - Performance-critical code - Simple type checking - Normal business logic

Performance Cost

// Direct access: ~1ns
user.Name

// Reflection: ~100ns+
reflect.ValueOf(user).FieldByName("Name").String()

Prefer Generics Over Reflection

// Go 1.18+: Use generics instead of reflect
func PrintSlice[T any](s []T) {
    for _, v := range s {
        fmt.Println(v)
    }
}

Summary

Approach Use Case
Type assertion Extract known concrete type
Type switch Handle multiple types
reflect Runtime introspection (last resort)
Generics Compile-time type safety

More examples

Example: safe comma-ok assertion

Save as main.go and go run . (with go mod init example if needed).

package main

import "fmt"

func asInt(v any) {
    if n, ok := v.(int); ok {
        fmt.Println("int:", n)
        return
    }
    fmt.Printf("not int: %T\n", v)
}

func main() {
    asInt(7)
    asInt("nope")
}

Expected:

int: 7
not int: string

Example: type switch

Save as main.go and go run . (with go mod init example if needed).

package main

import "fmt"

func classify(v any) {
    switch x := v.(type) {
    case int:
        fmt.Println("int", x*2)
    case string:
        fmt.Println("string len", len(x))
    default:
        fmt.Printf("other %T\n", x)
    }
}

func main() {
    classify(3)
    classify("go")
    classify(true)
}

Expected:

int 6
string len 2
other bool

Runnable example

Save as main.go. Then:

go mod init example
go run .
package main

import (
    "fmt"
    "reflect"
)

func describe(i any) {
    switch v := i.(type) {
    case int:
        fmt.Println("int:", v*2)
    case string:
        fmt.Println("string len:", len(v))
    case bool:
        fmt.Println("bool not:", !v)
    default:
        fmt.Printf("unknown: %T\n", v)
    }
}

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

func main() {
    var i any = "hello"
    s, ok := i.(string)
    fmt.Println("assert string:", s, ok)
    n, ok := i.(int)
    fmt.Println("assert int:", n, ok)

    describe(21)
    describe("go")
    describe(true)
    describe(3.14)

    // Reflection: useful for tags/serialization; prefer assertions/generics elsewhere.
    u := User{Name: "Ada", Age: 36}
    t := reflect.TypeOf(u)
    for j := 0; j < t.NumField(); j++ {
        f := t.Field(j)
        fmt.Printf("field %s json=%q\n", f.Name, f.Tag.Get("json"))
    }
}

Expected output:

assert string: hello true
assert int: 0 false
int: 42
string len: 2
bool not: false
unknown: float64
field Name json="name"
field Age json="age"

What to notice: The comma-ok form never panics; a type switch is clearer than a chain of assertions. Reflection can read struct tags but costs more and loses static checking.

Try next: Replace the default branch with a generic PrintSlice[T any] for slice values, or make a failing assertion i.(int) without ok and observe the panic.