Go/
Lesson

Interfaces give you flexibility by hiding concrete types behind contracts. But sometimes you need to peek inside -- to get the actual type back. AI generates type assertions frequently, especially when working with JSONWhat is json?A text format for exchanging data between systems. It uses key-value pairs and arrays, and every programming language can read and write it., generic containers, or any parameters. Your job is evaluating whether the assertionWhat is assertion?A statement in a test that checks whether a value matches what you expected, failing the test if it does not. is safe and whether the code should have used a stronger type in the first place.

Type assertions: opening the box

When you have an interface value, you can assert it holds a specific concrete type:

var i any = "hello"

s := i.(string)
fmt.Println(s)  // hello

The syntax i.(Type) says: "I believe i contains a Type. Give it to me."

But if you're wrong:

var i any = 42

s := i.(string)  // PANIC: interface conversion: int is not string
AI pitfall
AI frequently generates the unsafe single-return form v := i.(Type) instead of the safe comma-ok form. Every time you see a type assertion without ok, treat it as a potential panic site. The only exception is when the assertion is genuinely impossible to fail (like right after a type switch case).
02

The comma-ok form: the only safe way

Just like checking if a map key exists, type assertions have a safe version:

var i any = "hello"

s, ok := i.(string)
if ok {
    fmt.Println("It's a string:", s)
} else {
    fmt.Println("Not a string")
}

The ok boolean tells you whether the assertionWhat is assertion?A statement in a test that checks whether a value matches what you expected, failing the test if it does not. succeeded. No panic, no crash.

FormBehavior on wrong typeWhen to use
v := i.(Type)PanicsOnly when type is guaranteed (after type switch)
v, ok := i.(Type)Sets ok = false, v is zero valueAlways prefer this
03

Type switches: the elegant alternative

Checking multiple types with if/else chains gets messy. Type switches are cleaner:

func Describe(v any) string {
    switch val := v.(type) {
    case string:
        return fmt.Sprintf("String: %s (len=%d)", val, len(val))
    case int:
        return fmt.Sprintf("Int: %d", val)
    case bool:
        return fmt.Sprintf("Bool: %t", val)
    case []int:
        return fmt.Sprintf("Int slice with %d elements", len(val))
    default:
        return fmt.Sprintf("Unknown: %T", val)
    }
}

Notice the special syntax: switch val := v.(type). Inside each case, val has the concrete type -- you get full type safety within the branch.

Type switches are more efficient than chains of comma-ok assertions. The compiler generates optimized code for checking multiple types. Use them whenever you handle more than two possible types.
04

The nil interface vs typed nil trap

This is the single most common type-assertionWhat is assertion?A statement in a test that checks whether a value matches what you expected, failing the test if it does not. bug in AI-generated Go code:

type Thing struct{}
func (t *Thing) Method() string { return "working" }

var t *Thing = nil
var i any = t  // i is NOT nil!

// Type assertion succeeds because the TYPE matches
thing, ok := i.(*Thing)
fmt.Println(ok)    // true
fmt.Println(thing) // <nil>

// But using thing will panic
thing.Method()  // panic: nil pointer dereference
AI pitfall
AI generates this exact pattern when returning errors or optional values through interfaces. The assertion succeeds (the type matches), but the underlying value is nil. Always add a nil check after a successful type assertion on pointer types:
> if thing, ok := i.(*Thing); ok && thing != nil {
>     // Now it's actually safe
>     thing.Method()
> }
>
05

Practical patterns AI generates

JSONWhat is json?A text format for exchanging data between systems. It uses key-value pairs and arrays, and every programming language can read and write it. unmarshaling

When AI unmarshals JSON into any, it generates type switches to process the result. Know what types JSON produces:

func ProcessJSON(data []byte) (string, error) {
    var result any
    if err := json.Unmarshal(data, &result); err != nil {
        return "", err
    }

    switch v := result.(type) {
    case map[string]any:
        return fmt.Sprintf("Object with %d keys", len(v)), nil
    case []any:
        return fmt.Sprintf("Array with %d items", len(v)), nil
    case string:
        return fmt.Sprintf("String: %s", v), nil
    case float64:  // JSON numbers are always float64
        return fmt.Sprintf("Number: %f", v), nil
    case bool:
        return fmt.Sprintf("Boolean: %t", v), nil
    case nil:
        return "null", nil
    default:
        return "", fmt.Errorf("unexpected type: %T", v)
    }
}
JSON typeGo type after json.Unmarshal into any
"hello"string
42 or 3.14float64 (always, even integers)
truebool
nullnil
{"key": "val"}map[string]any
[1, 2, 3][]any
AI pitfall
AI sometimes asserts JSON numbers as int instead of float64. JSON numbers always unmarshal to float64 when the target is any. If you need an integer, assert float64 first, then convert: int(v).

Interface checking with type assertionWhat is assertion?A statement in a test that checks whether a value matches what you expected, failing the test if it does not.

You can assert against an interface, not just a concrete type:

func FormatValue(v any) string {
    // Check if it implements fmt.Stringer
    if s, ok := v.(fmt.Stringer); ok {
        return s.String()
    }
    return fmt.Sprintf("%v", v)
}

This pattern is useful for checking optional capabilities -- "does this value also support string formatting?"

Safe type conversion utility

func ToString(v any) (string, bool) {
    switch s := v.(type) {
    case string:
        return s, true
    case []byte:
        return string(s), true
    case fmt.Stringer:
        return s.String(), true
    case error:
        return s.Error(), true
    default:
        return "", false
    }
}
06

When type assertions signal a design problem

If you see AI generating lots of type assertions, step back and ask: should this code use a specific type instead of any?

SituationType assertionsBetter approach
Processing unknown JSONNecessaryUse json.Unmarshal into a known struct
Generic containerNecessary (pre-generics)Use generics ([T any]) since Go 1.18
Function with 2-3 possible typesAcceptable via type switchConsider separate functions or an interface
Every function takes anyCode smellDefine proper interfaces or concrete types
Checking optional behaviorGood (assert interface)This is idiomatic
AI pitfall
AI trained on pre-1.18 Go code generates any parameters with type switches where generics would be cleaner. If you see a function that takes any and immediately type-switches to handle int, float64, and string uniformly, ask: "Could this be func Process[T constraints.Ordered](v T)?"
07

Quick reference

PatternSyntaxUse case
Unsafe assertionv := i.(Type)Only when type is guaranteed
Safe assertionv, ok := i.(Type)Always prefer this
Type switchswitch v := i.(type)Multiple type checks
Interface assertionv, ok := i.(SomeInterface)Check optional capabilities
Nil-safe pointer assertionv, ok := i.(*T); ok && v != nilHandle nil pointers in interfaces