Go interface conversion panic — interface {} is X, not Y
interface conversion: interface is X, not Y
Verified against Go spec: Type assertions, Go spec: Type switches, Go docs: encoding/json · Updated June 2026
> quick_fix
A type assertion `v.(T)` panics if the interface value's concrete type is not `T`. Use the two-return form `v, ok := x.(T)` — if `ok` is false, the assertion failed without panicking, and `v` is the zero value of T.
// Panics if data is not a string
func process(data interface{}) {
s := data.(string) // panic: interface {} is int, not string
fmt.Println(s)
}
// Fixed — comma-ok idiom
func process(data interface{}) {
s, ok := data.(string)
if !ok {
fmt.Printf("expected string, got %T\n", data)
return
}
fmt.Println(s)
}What causes this error
A type assertion `x.(T)` at runtime checks whether the concrete type stored in interface `x` is exactly `T`. If it's not, it panics with 'interface conversion: interface {} is {actual}, not {expected}'. This happens when asserting the wrong type on `interface{}` values from JSON decoding, function parameters, or context values. The single-return form `x.(T)` always panics on failure; the two-return form `x, ok := x.(T)` returns `false` for `ok` without panicking.
How to fix it
- 01
step 1
Use the comma-ok idiom for safe type assertions
The two-return form of type assertion never panics. Always use it when you're not certain of the concrete type.
var val interface{} = 42 // BAD — panics if val is not string s := val.(string) // GOOD — safe assertion s, ok := val.(string) if !ok { log.Printf("expected string, got %T: %v", val, val) return } - 02
step 2
Use a type switch for multiple possible types
When a value could be one of several types, a `switch v := x.(type)` handles each case cleanly without risking a panic.
func describe(val interface{}) string { switch v := val.(type) { case string: return fmt.Sprintf("string: %q", v) case int: return fmt.Sprintf("int: %d", v) case float64: return fmt.Sprintf("float64: %f", v) case bool: return fmt.Sprintf("bool: %t", v) case nil: return "nil" default: return fmt.Sprintf("unknown type: %T", v) } } - 03
step 3
Understand JSON decoding types for interface{}
When `json.Unmarshal` decodes into `interface{}`, it maps JSON types to specific Go types. JSON numbers become `float64`, not `int`. JSON objects become `map[string]interface{}`, not a struct. Asserting the wrong type on a decoded value is one of the most common sources of this panic.
var data interface{} json.Unmarshal([]byte(`{"count": 42}`), &data) m := data.(map[string]interface{}) // ✅ JSON objects → map[string]interface{} count := m["count"].(int) // ❌ JSON numbers → float64, not int! // Fix: count := m["count"].(float64) // ✅ correct Go type for JSON number countInt := int(count) // convert to int after assertion // Better: unmarshal into a typed struct instead of interface{} type Response struct { Count int `json:"count"` } var r Response json.Unmarshal([]byte(`{"count": 42}`), &r) fmt.Println(r.Count) // int, no assertion needed - 04
step 4
Avoid using interface{} / any as a shortcut — use typed parameters
If you find yourself writing many type assertions, the function signature should use the actual type. `interface{}` parameters require runtime type checking; typed parameters are checked at compile time.
// BAD — requires runtime type assertion everywhere func save(data interface{}) error { user, ok := data.(User) if !ok { return errors.New("not a user") } return db.Save(user) } // GOOD — compile-time type safety func save(user User) error { return db.Save(user) } // For generic behavior across types, use generics (Go 1.18+): func save[T User | Product](item T) error { return db.Save(item) }
How to verify the fix
- All type assertions use the comma-ok form or are inside a type switch.
- No single-return assertions `x.(T)` remain unless you are 100% certain of the concrete type.
- JSON decoding uses typed structs instead of `interface{}`.
Why interface conversion: interface {} is X, not Y happens at the runtime level
Go interfaces store a (type, value) pair internally. A type assertion `x.(T)` checks whether the stored type pointer matches `T`'s type descriptor. If they don't match, the single-return form calls `runtime.panicdottype` which throws the panic with the concrete type name from the stored type pointer. The two-return form instead stores `false` in the ok return and returns the zero value of T without panicking. The runtime overhead of both forms is identical — a pointer comparison — so there is no performance reason to prefer the unsafe single-return form.
Common debug mistakes for interface conversion: interface {} is X, not Y
- Asserting `.(int)` on a JSON-decoded number — `encoding/json` always decodes numbers to `float64` when the target is `interface{}`.
- Asserting `.(map[string]string)` on a JSON-decoded object — JSON objects decode to `map[string]interface{}`, not `map[string]string`.
- Using context.Value() without the comma-ok form — if the key doesn't exist or was stored with a different type, it panics.
- Asserting on a struct type when the interface holds a pointer to that struct — `.(User)` vs `(*User)` are different concrete types.
- Forgetting that a nil concrete pointer inside an interface is NOT a nil interface — asserting on a 'nil but typed' interface value succeeds but returns a nil pointer.
When interface conversion: interface {} is X, not Y signals a deeper problem
Frequent type assertions on `interface{}` in a codebase indicate insufficient use of Go's type system. Every `interface{}` that requires a type assertion at the call site is a missed opportunity for a typed API. Go 1.18 generics eliminate most legitimate uses of `interface{}`/`any` as a universal container — use `[T any]` for generic functions instead. The remaining legitimate use cases for `interface{}` are serialization boundaries (JSON, gRPC, plugin systems) and context values — and even there, typed wrapper functions should handle the assertion internally so callers never write `.(T)` directly.
Editor's take
The interface conversion panic is the Go runtime's way of telling you that your program's type assumptions are wrong. The most common production scenario: a middleware layer stores a value in `context.Context` as one type, and a handler retrieves it asserting a different type. Perhaps the value was stored as `*AuthUser` but is retrieved as `AuthUser` (missing the pointer). Or a previous developer refactored the stored type from a struct to an ID string, and the handler asserting the struct type wasn't updated. Context-stored values have no type safety at all — the compiler can't catch this mismatch.
The conventional fix for context values is typed accessor functions that hide the assertion: instead of `ctx.Value(userKey).(AuthUser)`, expose `GetAuthUser(ctx context.Context) (AuthUser, bool)` which does the assertion internally and returns comma-ok style. This makes callers type-safe and makes the assertion visible in one place. When the type changes, you update one function, not every call site.
The JSON decoding version of this panic is extremely common in teams coming from dynamic languages. In Python and JavaScript, a number from JSON is just a number. In Go, `json.Unmarshal` into `interface{}` maps JSON numbers to `float64` regardless of whether the value is an integer. Every JavaScript developer who writes Go for the first time hits `panic: interface conversion: interface {} is float64, not int` within their first week. The fix is always the same: use typed structs for JSON decoding, and if you must work with raw `interface{}` maps, convert after asserting: `int(v["count"].(float64))`.
By Bikram Nath · Curator · Updated June 2026
Frequently asked questions
When is the single-return type assertion x.(T) safe to use?
Only when you have a compile-time or prior-assertion guarantee that the type is correct. For example, after a type switch's `case string:` arm, asserting `.(string)` is redundant but safe. Or when you define the interface value yourself in the same function and know its concrete type. In all other cases, use the comma-ok form.
How does a type assertion differ from a type conversion?
Type assertions (`x.(T)`) extract the concrete value from an interface — they work at runtime and can panic. Type conversions (`T(x)`) convert between compatible concrete types — they're mostly compile-time checked (numeric conversions, string/[]byte, etc.) and don't panic. They're completely different operations despite similar syntax.
Can I assert a nil interface value?
A nil interface has no concrete type, so `nil.(string)` always panics. The comma-ok form returns `"", false` safely. If a value could be nil, check `if val == nil` before asserting, or rely on the comma-ok form which returns `false` for nil.