Go Interface Conversion Panic (interface is X, Not Y)
An interface conversion panic means a type assertion assumed an interface value held a specific concrete type, but at runtime it actually held a different one — Go's type system can't catch this at compile time when working with interfaces, so the mismatch only surfaces when the code actually runs.
The Problem
The program crashes with:
panic: interface conversion: interface {} is string, not int
goroutine 1 [running]:
main.processValue(...)
/app/main.go:12
The exact line points to a type assertion that assumed the wrong concrete type for whatever value the interface actually held at that point in execution.
Why It Happens
An interface value in Go internally holds both a concrete type and a value. A type assertion using the single-value form (x.(SomeType)) tells the compiler "I'm certain this holds this specific type" — and if that assumption is wrong at runtime, Go panics rather than silently producing an incorrect result. Common causes:
- Data coming from an external source (JSON decoding into
interface{}, a map with mixed value types, reflection-based code) genuinely contains a different type than assumed, often because the actual data shape differs from what the code was originally written to expect - A function's return type changed at some point (returning a different concrete type than before) without every caller doing the type assertion being updated to match
- JSON numbers specifically — when unmarshaled into
interface{}, JSON numbers becomefloat64in Go by default, notint, which surprises people expecting whole numbers in JSON to become Go'sinttype automatically - A generic or reusable function handling multiple possible types makes an incorrect assumption about which specific type it's actually been given for a specific call, especially in code without exhaustive handling of every case it should support
The Fix
Use the two-value form of type assertion, which returns a boolean indicating success instead of panicking on failure:
value, ok := v.(int)
if !ok {
// handle the unexpected type gracefully instead of crashing
log.Printf("expected int, got %T", v)
return
}
// safe to use value as int here
This single change — checking ok instead of assuming success — converts a program-crashing panic into a normal, handleable error condition, and should be the default approach anywhere the concrete type genuinely isn't guaranteed with full certainty.
For the common JSON-into-interface{} case specifically, remember that numbers decode as float64, not int, unless you're using a custom decoder or a specific struct with typed fields instead of a generic interface{}:
var data map[string]interface{}
json.Unmarshal(jsonBytes, &data)
// Wrong - will panic if the JSON number decoded as float64
count := data["count"].(int)
// Correct
count, ok := data["count"].(float64)
if ok {
intCount := int(count)
}
Where possible, avoid interface{} entirely in favor of a properly typed struct, which sidesteps this entire category of issue by having the JSON decoder handle type conversion correctly and consistently at unmarshal time, rather than deferring type resolution to manual assertions scattered throughout your code:
type Payload struct {
Count int `json:"count"`
}
var p Payload
json.Unmarshal(jsonBytes, &p)
// p.Count is a proper int, no assertion needed at all
For code that genuinely needs to handle multiple possible concrete types dynamically, use a type switch rather than a chain of individual assertions, which is both cleaner and forces you to consider every case explicitly:
switch v := value.(type) {
case int:
fmt.Println("int:", v)
case string:
fmt.Println("string:", v)
default:
fmt.Printf("unexpected type: %T\n", v)
}
Still Not Working?
If the panic happens deep inside a library or generic utility function rather than your own directly written type assertion, use the panic's stack trace to identify exactly which call site passed the unexpected type, and add a temporary %T format verb log statement right before that call to print the actual runtime type being passed in — this quickly clarifies whether the bug is in the caller providing the wrong type, or in the receiving function's assumption about what types it should accept.