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 string
  • panic: interface conversion: interface {} is nil, not *myapp.User
  • panic: 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 as int
  • 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.Unmarshaler for complex custom type handling
  • Validate types at API boundaries, not deep in business logic
  • Use static analysis tools (staticcheck, govet) to catch unsafe assertions
  • **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