Introduction
Go interface conversion panic occurs when a type assertion fails at runtime, causing the application to crash with panic: interface conversion: interface {} is <actual-type>, not <expected-type>. This error happens when code asserts that an interface{} value holds a specific concrete type, but the actual underlying type differs. Common causes include type asserting to wrong concrete type, interface{} holding nil vs typed nil values, JSON unmarshaling numbers as float64 not int, map[string]interface{} values asserted incorrectly, type assertion on nil interface (not nil value with type), missing comma-ok idiom for safe assertion, database scan returning different type than expected, gRPC/protobuf messages with unexpected types, reflection-based code with incorrect type assumptions, and Go 1.18+ generics with incorrect type parameters. The fix requires understanding Go's interface representation (type descriptor + value), using safe assertion patterns (comma-ok, type switch), and validating types before conversion. This guide provides production-proven patterns for safe interface handling across JSON processing, database operations, RPC calls, and generic code.
Symptoms
panic: interface conversion: interface {} is int, not stringpanic: interface conversion: interface {} is nil, not *myapp.Userpanic: interface conversion: interface {} is float64, not int64- Application crashes intermittently based on data content
panic: interface conversion: interface {} is map[string]interface {}, not myapp.Struct- JSON unmarshal followed by type assertion panics
- Database query results fail type assertion
- gRPC handler panics on message type conversion
- Generic function panics with type constraint violation
panic: interface conversion: interface {} is []interface {}, not []string
Common Causes
- Type assertion to incorrect concrete type
- JSON numbers unmarshaled as
float64, asserted asint - Map values stored as
interface{}, asserted incorrectly - Nil interface vs typed nil confusion
- Type assertion without comma-ok safety check
- Database driver returns different type than documented
- Slice of interfaces not convertible to slice of concrete types
- Reflection code sets wrong type in interface{}
- Generics type parameter doesn't match constraint
- Third-party library returns unexpected types
Step-by-Step Fix
### 1. Diagnose interface type at runtime
Identify actual underlying type:
```go // Print actual type and value func inspectInterface(v interface{}) { if v == nil { fmt.Println("Value is nil") return }
t := reflect.TypeOf(v) fmt.Printf("Type: %T\n", v) // Concrete type fmt.Printf("Value: %v\n", v) // Actual value fmt.Printf("Kind: %v\n", t.Kind()) // Kind (int, string, struct, etc.) fmt.Printf("Name: %v\n", t.Name()) // Type name }
// Usage var data interface{} data = 42 inspectInterface(data) // Output: Type: int, Value: 42, Kind: int, Name: int
data = float64(42) inspectInterface(data) // Output: Type: float64, Value: 42, Kind: float64, Name: float64 ```
Debug panic location:
```go // Add recovery to catch and log panic func safeFunction() (err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("panic recovered: %v", r)
// Get stack trace buf := make([]byte, 4096) n := runtime.Stack(buf, false) log.Printf("Panic stack: %s", buf[:n]) } }()
// Code that might panic value := getData() // Returns interface{} result := value.(string) // Potential panic log.Printf("Result: %s", result)
return nil } ```
Check for typed nil:
```go // Interface is nil only if BOTH type and value are nil var v1 interface{} = nil // v1 == nil is true var v2 *int = nil var v3 interface{} = v2 // v3 == nil is FALSE (has type *int)
func checkNil(v interface{}) bool { if v == nil { return true // Definitely nil }
// Check for typed nil using reflection rv := reflect.ValueOf(v) switch rv.Kind() { case reflect.Ptr, reflect.Map, reflect.Array, reflect.Slice, reflect.Chan, reflect.Func, reflect.Interface: return rv.IsNil() default: return false } }
// Usage var ptr *int = nil var iface interface{} = ptr fmt.Println(iface == nil) // false - has type *int fmt.Println(checkNil(iface)) // true - value is nil ```
### 2. Fix type assertion syntax
Use comma-ok idiom for safe assertion:
```go // WRONG: Direct assertion panics on type mismatch func processWrong(data interface{}) string { return data.(string) // PANIC if data is not string }
// CORRECT: Comma-ok returns ok false on mismatch func processSafe(data interface{}) string { str, ok := data.(string) if !ok { log.Printf("Expected string, got %T", data) return "" // Or return error, default value, etc. } return str }
// Usage var data interface{} = 42 result := processSafe(data) // Logs: "Expected string, got int" // Returns: "" ```
Handle multiple possible types:
go
// Type switch for multiple types
func handleMultiType(data interface{}) {
switch v := data.(type) {
case string:
fmt.Printf("String: %s\n", v)
case int:
fmt.Printf("Int: %d\n", v)
case int64:
fmt.Printf("Int64: %d\n", v)
case float64:
fmt.Printf("Float64: %f\n", v)
case []interface{}:
fmt.Printf("Slice: %v\n", v)
case map[string]interface{}:
fmt.Printf("Map: %v\n", v)
case *myapp.User:
fmt.Printf("User: %+v\n", v)
default:
fmt.Printf("Unknown type: %T\n", v)
}
}
Assert pointer types correctly:
```go // WRONG: Asserting value type when interface holds pointer var data interface{} = &myapp.User{Name: "test"} user := data.(myapp.User) // PANIC: interface holds *myapp.User
// CORRECT: Assert to pointer type userPtr := data.(*myapp.User) fmt.Println(userPtr.Name)
// Or use type switch to handle both switch u := data.(type) { case myapp.User: fmt.Println(u.Name) case *myapp.User: fmt.Println(u.Name) } ```
### 3. Fix JSON unmarshaling type issues
Handle JSON numbers correctly:
go
// WRONG: JSON numbers unmarshal to float64
type Response struct {
Data interface{} json:"data"`
}
var resp Response
json.Unmarshal([]byte({"data": 42}), &resp)
count := resp.Data.(int) // PANIC: data is float64
// CORRECT 1: Use float64 assertion countFloat := resp.Data.(float64) count := int64(countFloat)
// CORRECT 2: Use json.Number to preserve type
decoder := json.NewDecoder(strings.NewReader({"data": 42}))
decoder.UseNumber() // Numbers stay as json.Number
decoder.Decode(&resp)
numStr := resp.Data.(json.Number).String() count, _ := resp.Data.(json.Number).Int64() ```
Unmarshal to concrete type:
go
// Best practice: Avoid interface{} for known structures
type Response struct {
Data UserData json:"data"` // Concrete type
}
type UserData struct {
Count int json:"count"
Name string json:"name"
}
var resp Response
err := json.Unmarshal([]byte({"data": {"count": 42, "name": "test"}}), &resp)
if err != nil {
log.Fatal(err)
}
// No type assertion needed - resp.Data is UserData
```
Handle map[string]interface{} from JSON:
go
// JSON unmarshals objects as map[string]interface{}
var data interface{}
json.Unmarshal([]byte({"name": "test", "age": 30}`), &data)
m := data.(map[string]interface{})
// Values are also interface{} - need safe assertion name := m["name"].(string) // OK for strings age := m["age"].(int) // PANIC! JSON numbers are float64 age := m["age"].(float64) // OK ageInt := int(m["age"].(float64)) // Convert to int
// Safe nested access func getString(m map[string]interface{}, key string) string { v, ok := m[key].(string) if !ok { return "" } return v }
func getInt(m map[string]interface{}, key string) int64 { switch v := m[key].(type) { case float64: return int64(v) case int: return int64(v) case int64: return v default: return 0 } } ```
### 4. Fix database type conversion
SQL driver type handling:
```go // Different drivers return different types for same SQL type // PostgreSQL (lib/pq): NUMERIC -> *big.Int or string // MySQL (go-sql-driver): INT -> int64 // SQLite (modernc): INTEGER -> int64
// WRONG: Assuming specific type var value interface{} db.QueryRow("SELECT count FROM users WHERE id = ?", 1).Scan(&value) count := value.(int) // May panic depending on driver
// CORRECT 1: Scan to concrete type var count int64 db.QueryRow("SELECT count FROM users WHERE id = ?", 1).Scan(&count)
// CORRECT 2: Use sql.NullInt64 for nullable var nullCount sql.NullInt64 db.QueryRow("SELECT count FROM users WHERE id = ?", 1).Scan(&nullCount) if nullCount.Valid { count := nullCount.Int64 }
// CORRECT 3: Handle interface{} safely var value interface{} db.QueryRow("SELECT count FROM users WHERE id = ?", 1).Scan(&value)
switch v := value.(type) { case int64: count := v case int32: count := int64(v) case int: count := int64(v) case []byte: // Some drivers return byte slices count, _ := strconv.ParseInt(string(v), 10, 64) default: log.Printf("Unexpected type: %T", v) } ```
Handle PostgreSQL specific types:
```go // PostgreSQL HSTORE, JSONB, ARRAY need special handling import "github.com/lib/pq"
// ARRAY - scan to slice var tags []string db.QueryRow("SELECT tags FROM posts WHERE id = ?", 1).Scan(&tags)
// HSTORE - scan to map var hstoreVal map[string]sql.NullString db.QueryRow("SELECT metadata FROM users WHERE id = ?", 1).Scan(&hstoreVal)
// JSONB - scan to json.RawMessage, then unmarshal var rawJSON json.RawMessage db.QueryRow("SELECT config FROM settings WHERE id = ?", 1).Scan(&rawJSON)
var config Config json.Unmarshal(rawJSON, &config) ```
### 5. Fix slice type assertions
Handle slice of interfaces:
```go // WRONG: Can't assert []interface{} to []string var data interface{} = []interface{}{"a", "b", "c"} strings := data.([]string) // PANIC: different types
// CORRECT 1: Iterate and convert ifaceSlice := data.([]interface{}) strings := make([]string, len(ifaceSlice)) for i, v := range ifaceSlice { strings[i] = v.(string) }
// CORRECT 2: Safe conversion with type check func toStringSlice(data interface{}) ([]string, error) { switch v := data.(type) { case []string: return v, nil case []interface{}: result := make([]string, 0, len(v)) for _, item := range v { str, ok := item.(string) if !ok { return nil, fmt.Errorf("non-string element: %T", item) } result = append(result, str) } return result, nil default: return nil, fmt.Errorf("unexpected type: %T", data) } } ```
Handle slice type conversion:
```go // Generic type-safe slice conversion (Go 1.18+) func convertSlice[T, U any](input []T, convert func(T) U) []U { result := make([]U, len(input)) for i, v := range input { result[i] = convert(v) } return result }
// Usage ifaceSlice := []interface{}{"a", "b", "c"} strSlice := convertSlice(ifaceSlice, func(i interface{}) string { return i.(string) }) ```
### 6. Fix nil interface issues
Understand nil interface representation:
```go // Interface is nil only when both type and value are nil var v1 interface{} = nil // v1 == nil: true var v2 *int = nil var v3 interface{} = v2 // v3 == nil: false (type is *int) var v4 interface{} = (*int)(nil) // v4 == nil: false (type is *int)
// This check fails even though value is nil pointer if v3 == nil { fmt.Println("nil") // Never prints }
// Correct nil check for interfaces that might hold pointers func isActuallyNil(v interface{}) bool { if v == nil { return true }
rv := reflect.ValueOf(v) switch rv.Kind() { case reflect.Ptr, reflect.Map, reflect.Array, reflect.Slice, reflect.Chan, reflect.Func, reflect.Interface: return rv.IsNil() default: return false } }
// Usage fmt.Println(isActuallyNil(v3)) // true ```
Handle optional values safely:
```go // WRONG: Direct assertion on potentially nil interface func getUser(id int) interface{} { user, err := db.FindUser(id) if err != nil { return nil // Returns untyped nil } return user // Returns *User }
user := getUser(1) if user != nil { // This check might fail for typed nil u := user.(*myapp.User) // Could panic }
// CORRECT 1: Return concrete type or error func getUser(id int) (*myapp.User, error) { user, err := db.FindUser(id) if err != nil { return nil, err } return user, nil }
user, err := getUser(1) if err != nil { // Handle error } if user != nil { // Safe to use }
// CORRECT 2: Use comma-ok with type assertion user, ok := getUser(1).(*myapp.User) if !ok { // Handle nil or wrong type } ```
### 7. Fix generic type assertion issues
Go 1.18+ generics type constraints:
```go // WRONG: Type assertion inside generic function func Process[T any](data interface{}) T { return data.(T) // May panic if T doesn't match }
// CORRECT: Use type parameter directly func Process[T any](data T) T { return data // Type-safe at compile time }
// For interface{} input with type parameter output func Convert[T any](data interface{}) (T, error) { var zero T result, ok := data.(T) if !ok { return zero, fmt.Errorf("cannot convert %T to %T", data, zero) } return result, nil }
// Usage with type constraint type Number interface { int | int8 | int16 | int32 | int64 | float32 | float64 }
func ToNumber[T Number](data interface{}) (T, error) { var zero T switch v := data.(type) { case T: return v, nil case int: return T(v), nil case int64: return T(v), nil case float64: return T(v), nil default: return zero, fmt.Errorf("unsupported type: %T", data) } } ```
Handle any type parameter:
```go // Type switch with generics func AssertType[T any](data interface{}) (T, error) { var zero T
result, ok := data.(T) if !ok { return zero, fmt.Errorf("type mismatch: got %T, want %T", data, zero) }
return result, nil }
// For pointer types func AssertPtr[T any](data interface{}) (*T, error) { result, ok := data.(*T) if !ok { return nil, fmt.Errorf("expected *%T, got %T", zero, data) } return result, nil } ```
Prevention
- Use comma-ok idiom for all type assertions on external data
- Prefer concrete types over
interface{}in structs and function signatures - Unmarshal JSON directly to concrete types, not
interface{} - Use
sql.Null*types for nullable database columns - Document expected types in function comments when using
interface{} - Add unit tests for type assertion edge cases
- Use generics (Go 1.18+) instead of
interface{}where possible - Implement
json.Unmarshalerfor complex custom type handling - Validate types at API boundaries, not deep in business logic
- Use static analysis tools (staticcheck, govet) to catch unsafe assertions
Related Errors
- **Go nil pointer dereference**: Dereferencing nil pointer, not interface issue
- **Go channel deadlock**: Goroutine synchronization issue
- **Go context deadline exceeded**: Timeout in context.WithTimeout or WithDeadline
- **Go goroutine leak**: Goroutines not exiting properly
- **panic: reflect**: Reflection operation on invalid type