goseverity: can-fix
interface conversion: interface {} is X, not Y

Go interface conversion panic — interface {} is X, not Y

interface conversion: interface is X, not Y

97% fixable~10 mindifficulty: intermediate

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.

> advertisementAdSense placeholder

How to fix it

  1. 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
    }
  2. 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)
        }
    }
  3. 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
  4. 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.

disclosure:Errordex runs AdSense, has zero third-party affiliate or sponsored links, and occasionally links to the editor’s own paid digital products (clearly labelled). Every fix is cross-referenced against the official sources listed in the “sources” sidebar before it ships. If a fix here didn’t work for you, please email so we can update the page.