πŸ”§ Error Fixes
Β· 6 min read
Last updated on

Go Interface Conversion Panic β€” How to Fix It


panic: interface conversion: interface {} is string, not int
panic: interface conversion: interface is nil, not main.MyType

This panic occurs when you perform a type assertion on an interface value, but the underlying type doesn’t match what you’re asserting. Go panics immediately rather than returning a zero value, which crashes your program.

Why this happens

In Go, interfaces hold a value and a type. A type assertion i.(T) extracts the concrete value of type T from the interface i. If i doesn’t actually hold a value of type T, Go panics.

This happens in three scenarios:

  1. The interface holds a different type than expected
  2. The interface is nil (holds no value at all)
  3. JSON unmarshaling stores unexpected types in interface{} values

Fix 1: Use the comma-ok pattern

The safest way to do type assertions. The second return value tells you whether the assertion succeeded.

// ❌ Direct assertion β€” panics if wrong type
var i interface{} = "hello"
n := i.(int)  // panic: interface conversion: interface {} is string, not int

// βœ… Comma-ok pattern β€” never panics
n, ok := i.(int)
if !ok {
    fmt.Println("i is not an int, it's:", reflect.TypeOf(i))
    return
}
fmt.Println("value:", n)

Rule of thumb: Always use comma-ok unless you’re 100% certain of the type (e.g., right after a type switch case).

This pattern works for any type assertion:

s, ok := i.(string)
m, ok := i.(map[string]interface{})
err, ok := i.(error)
r, ok := i.(io.Reader)

Fix 2: Check for nil before asserting

A nil interface has no underlying value or type. Any type assertion on nil panics.

// ❌ Asserting on nil interface
var i interface{}  // nil
s := i.(string)    // panic: interface conversion: interface is nil, not string

// βœ… Check for nil first
if i == nil {
    fmt.Println("interface is nil, using default")
    return ""
}
s, ok := i.(string)
if !ok {
    return ""
}
return s

Common source of nil interfaces: Functions that return interface{} or error β€” when the function returns nil, the caller might still try to assert a type on it.

func getConfig(key string) interface{} {
    val, exists := configMap[key]
    if !exists {
        return nil  // Caller must check for nil before asserting
    }
    return val
}

// ❌ Panics if key doesn't exist
host := getConfig("host").(string)

// βœ… Safe
if val := getConfig("host"); val != nil {
    host, _ = val.(string)
}

Fix 3: Use a type switch for multiple possible types

When an interface could hold several types, a type switch is cleaner and safer than multiple comma-ok assertions.

func processValue(i interface{}) string {
    switch v := i.(type) {
    case string:
        return v
    case int:
        return strconv.Itoa(v)
    case float64:
        return fmt.Sprintf("%.2f", v)
    case bool:
        return strconv.FormatBool(v)
    case nil:
        return "<nil>"
    case []interface{}:
        return fmt.Sprintf("array with %d items", len(v))
    default:
        return fmt.Sprintf("unknown type: %T", v)
    }
}

Type switches never panic β€” the default case catches anything unexpected. Use them when processing data from external sources (APIs, config files, user input).

Fix 4: JSON unmarshaling stores float64, not int

This is one of the most common Go gotchas. When you unmarshal JSON into interface{} or map[string]interface{}, all numbers become float64 β€” never int.

var data map[string]interface{}
json.Unmarshal([]byte(`{"count": 42, "name": "test"}`), &data)

// ❌ JSON numbers are float64, not int
count := data["count"].(int)  // panic!

// βœ… Assert to float64, then convert
count := int(data["count"].(float64))

// βœ… Better: unmarshal into a typed struct
type Response struct {
    Count int    `json:"count"`
    Name  string `json:"name"`
}
var resp Response
json.Unmarshal([]byte(`{"count": 42, "name": "test"}`), &resp)
// resp.Count is already int

JSON type mapping to Go interface{}:

JSON typeGo type
numberfloat64
stringstring
booleanbool
nullnil
array[]interface{}
objectmap[string]interface{}

Best practice: Avoid map[string]interface{} when you know the structure. Define a struct with proper types and let json.Unmarshal handle the conversion.

Fix 5: Interface not implemented (compile-time error)

This is a different kind of interface error β€” it happens at compile time, not runtime. But it’s related and often confused with the panic.

type Writer interface {
    Write(data []byte) (int, error)
}

type MyLogger struct{}

// ❌ Compile error: MyLogger does not implement Writer
var w Writer = MyLogger{}

// βœ… Implement the method with the exact signature
func (m MyLogger) Write(data []byte) (int, error) {
    fmt.Print(string(data))
    return len(data), nil
}

var w Writer = MyLogger{}  // Now works

Tip: Use a compile-time check to verify interface implementation:

// This line fails to compile if MyLogger doesn't implement Writer
var _ Writer = (*MyLogger)(nil)

Fix 6: Pointer vs value receiver mismatch

A subtle variant β€” your type implements the interface with a pointer receiver, but you’re using a value.

type Stringer interface {
    String() string
}

type User struct{ Name string }

// Method on *User (pointer receiver)
func (u *User) String() string {
    return u.Name
}

// ❌ Value doesn't implement the interface (pointer does)
var s Stringer = User{Name: "Alice"}  // Compile error!

// βœ… Use a pointer
var s Stringer = &User{Name: "Alice"}  // Works

Fix 7: Recovering from interface panics

If you can’t guarantee the type at compile time (e.g., processing plugin output), use recover as a last resort:

func safeAssert(i interface{}) (result string, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("type assertion failed: %v", r)
        }
    }()
    
    result = i.(string)
    return result, nil
}

Warning: Using recover to handle type assertions is a code smell. Prefer comma-ok or type switches. Only use recover at boundaries where you process untrusted data.

Debugging tips

  1. Print the actual type before asserting:

    fmt.Printf("type: %T, value: %v\n", i, i)
  2. Use reflect.TypeOf for more detail:

    fmt.Println(reflect.TypeOf(i))  // Shows the concrete type
  3. Check if nil β€” fmt.Printf("%v", i) prints <nil> for nil interfaces.

  4. Stack trace β€” The panic message includes the file and line number. Look at what’s being assigned to the interface upstream.

FAQ

What’s the difference between a nil interface and an interface holding a nil pointer?

They’re different! A nil interface has no type or value. An interface holding a nil pointer has a type but a nil value β€” and it’s NOT equal to nil.

var i interface{} = (*string)(nil)
fmt.Println(i == nil)  // false! The interface holds a type (*string)

This is a notorious Go gotcha. See the Go nil pointer dereference fix for more.

Can I assert to an interface type?

Yes. You can assert that a value implements another interface:

var i interface{} = os.Stdout
w, ok := i.(io.Writer)  // true β€” *os.File implements io.Writer

Does the comma-ok pattern have a performance cost?

No measurable cost. The compiler generates the same type check either way β€” the only difference is whether it panics or returns false.

How do I handle this in generic code (Go 1.18+)?

With generics, you can often avoid interface{} entirely:

func process[T any](val T) T {
    // No type assertion needed β€” T is known at compile time
    return val
}