Skip to content

Fix: Go cannot use X (type Y) as type Z in argument

FixDevs · (Updated: )

Part of:  Go, Rust & Systems Errors

Quick Answer

How to fix Go 'cannot use as type' error caused by type mismatches, interface satisfaction, pointer vs value receivers, type conversions, and generic constraints.

The Error

You compile Go code and get:

cannot use myVar (variable of type string) as int value in argument to processNumber

Or variations:

cannot use &myStruct (value of type *MyStruct) as MyInterface value in variable declaration:
*MyStruct does not implement MyInterface (missing method DoSomething)
cannot use mySlice (variable of type []int) as []interface{} value in argument
cannot use 42 (untyped int constant) as string value in return statement
cannot use myFunc (value of type func(int)) as func(interface{}) value

Go’s type system is strict. You passed a value of one type where a different type is expected, and Go does not implicitly convert between them.

Why This Happens

Go has no implicit type conversions. Unlike JavaScript or Python, Go does not automatically convert an int to a string, a []int to []interface{}, or a float64 to an int. You must convert explicitly. The language designers chose this on purpose — explicit conversions force you to think about precision loss, sign mismatches, and pointer aliasing rather than letting them slip in silently.

Additionally, Go interfaces are satisfied structurally (no explicit implements keyword), but the satisfaction rules are strict about pointer vs value receivers. A type T with pointer-receiver methods only satisfies an interface as *T, not as T. This catches many developers coming from Java or C# where the implements keyword makes the contract visible at the declaration site. In Go, the compiler only complains at the assignment or call site, often far from the method definition.

A third source of confusion is named types. Declaring type UserID int produces a brand-new type that the compiler treats as distinct from int and from every other type X int. This is what lets time.Duration reject raw integers and prevents you from passing a UserID where an OrderID is expected — but it surprises developers who expect Go to be structurally typed everywhere. The same applies to function types, slice types, and map types: []int and []MyInt are not interchangeable even though MyInt’s underlying type is int.

Common causes:

  • Wrong argument type. Passing a string where an int is expected.
  • Missing interface method. A type does not implement all methods of an interface.
  • Pointer vs value receiver mismatch. A method is defined on *T but you pass T (not a pointer).
  • Slice type mismatch. []int is not []interface{} even though int satisfies interface{}.
  • Named type vs underlying type. type MyInt int is a different type from int.
  • Dependency version drift. A type’s signature changed after go get -u and now your code does not match the new interface.
  • Same name, different package paths. Two packages both export type Config struct{} and you imported the wrong one.

In Production: Incident Lens

This is a compile-time error, so the production failure mode is “the deploy did not happen.” That is the good news. The bad news is that the error often surfaces after go get -u or after a dependency upgrade in a PR that nobody scrutinizes, and you discover it the moment you try to ship the next unrelated change.

How it surfaces: the CI build turns red on a Friday afternoon merge. The error message points at a line you did not touch — usually an interface satisfaction check in a _test.go file or a constructor that wires up a dependency. Tracing back through the diff reveals that go.sum changed because another PR ran go mod tidy and pulled in a minor version of a library that adjusted its interface. The breaking change might be as subtle as a method gaining a context.Context parameter or returning error instead of (any, error).

Blast radius: scoped to “no deploys until someone fixes it.” Nothing in production is degraded yet, but every other change in the queue is now blocked behind this one. Hotfixes for unrelated incidents become impossible until the type mismatch is resolved or the dep is rolled back. If the failing build is the trunk branch, every developer’s local go build starts failing too once they pull.

Monitoring signal: there is no runtime monitoring for this — by definition the binary never runs. The signal is the CI pipeline failure plus a notification from your dep-update bot (Dependabot, Renovate). What you actually want is dependency drift visibility: a dashboard that shows which transitive deps have updated since the last successful build and whether their public API has changed. Tools like gorelease from the official Go team can detect API-incompatible changes between two versions of a module.

Recovery sequence: the fastest fix is usually to pin the dependency to its previous version. Run go get [email protected] to revert to the version that worked, commit go.mod and go.sum, and unblock the pipeline. Then schedule a separate PR that adapts your code to the new interface — do not try to fix the upgrade and ship a feature in the same PR. If the offending dep is one of yours (a shared internal module), publish a patch release that restores the old signature and deprecates it.

Postmortem preventive: add go vet ./... and staticcheck ./... to CI as required checks. Run go mod tidy and fail the build if go.mod or go.sum changes — this catches accidental version bumps that snuck in without anyone noticing. Use a dep-update strategy that lock-steps minor versions ({"updateTypes": ["minor", "patch"], "groupName": "all"} in Renovate) so a single PR upgrades everything together and you see all type breakage at once instead of trickled in over weeks. For libraries you do not control, pin major versions strictly and review the changelog before any go get -u.

Fix 1: Use Explicit Type Conversion

Go requires explicit conversions between compatible types:

var x int = 42
var y float64 = float64(x)   // int → float64
var z string = strconv.Itoa(x)  // int → string (not string(x)!)

var f float64 = 3.14
var i int = int(f)   // float64 → int (truncates decimal)

var s string = "hello"
var b []byte = []byte(s)  // string → []byte
var s2 string = string(b)  // []byte → string

Common mistake — string(int):

s := string(65)     // "A" — converts int to Unicode codepoint, NOT "65"!
s := strconv.Itoa(65)  // "65" — converts int to its string representation

Pro Tip: Use strconv for numeric ↔ string conversions. string(n) interprets n as a Unicode codepoint, which is almost never what you want. strconv.Itoa(n) converts the number to its decimal string representation.

Fix 2: Fix Interface Satisfaction

If the error says a type does not implement an interface:

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

type MyWriter struct{}

func (w MyWriter) Write(data string) error {  // Wrong signature!
    return nil
}

var w Writer = MyWriter{}
// cannot use MyWriter{} as Writer value: MyWriter does not implement Writer
// (wrong type for method Write)

The method signature must exactly match the interface method:

func (w MyWriter) Write(data []byte) (int, error) {
    fmt.Print(string(data))
    return len(data), nil
}

Parameter types, return types, and the number of parameters must all match exactly.

Fix 3: Fix Pointer vs Value Receiver

Methods defined on *T (pointer receiver) are only available to pointer values when satisfying interfaces:

Broken:

type Stringer interface {
    String() string
}

type User struct {
    Name string
}

func (u *User) String() string {  // Pointer receiver
    return u.Name
}

var s Stringer = User{Name: "Alice"}  // ERROR!
// cannot use User{} as Stringer value: User does not implement Stringer
// (method String has pointer receiver)

Fixed — use a pointer:

var s Stringer = &User{Name: "Alice"}  // Pointer — works!

Or use a value receiver:

func (u User) String() string {  // Value receiver
    return u.Name
}

var s Stringer = User{Name: "Alice"}  // Works — value receiver
var s Stringer = &User{Name: "Alice"} // Also works — pointer can use value receivers

Rule of thumb:

ReceiverValue satisfies interface?Pointer satisfies interface?
func (t T)YesYes
func (t *T)NoYes

Common Mistake: Defining methods with pointer receivers but trying to store values (not pointers) in interface variables. If any method uses a pointer receiver, you must use a pointer to satisfy the interface. Consistency helps — use either all pointer receivers or all value receivers for a type.

Fix 4: Fix Slice Type Mismatches

Go slices are not covariant. []int is not []interface{} even though int satisfies interface{}:

Broken:

func printAll(items []interface{}) {
    for _, item := range items {
        fmt.Println(item)
    }
}

numbers := []int{1, 2, 3}
printAll(numbers)
// cannot use numbers (variable of type []int) as []interface{} value

Fixed — use generics (Go 1.18+):

func printAll[T any](items []T) {
    for _, item := range items {
        fmt.Println(item)
    }
}

numbers := []int{1, 2, 3}
printAll(numbers)  // Works!

Fixed — convert manually:

numbers := []int{1, 2, 3}
items := make([]interface{}, len(numbers))
for i, n := range numbers {
    items[i] = n
}
printAll(items)

Why this limitation exists: In Go, []int and []interface{} have different memory layouts. An int is stored directly in the slice element; an interface{} stores a type descriptor and a pointer. Go cannot reinterpret the memory, so an explicit conversion is required.

Fix 5: Fix Named Type Conversions

Named types are distinct from their underlying types:

type UserID int
type OrderID int

func processUser(id UserID) {}

var orderID OrderID = 42
processUser(orderID)
// cannot use orderID (variable of type OrderID) as UserID value

Fix: Convert explicitly:

processUser(UserID(orderID))

Named types with the same underlying type can be converted between each other, but Go does not do it implicitly. This is a feature — it prevents mixing up IDs of different entity types.

Fix 6: Fix Function Type Mismatches

Function types must match exactly:

type Handler func(w http.ResponseWriter, r *http.Request)

func myHandler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello"))
}

var h Handler = myHandler  // Works — exact match

// But:
type GenericHandler func(interface{})

func specificHandler(s string) {}

var g GenericHandler = specificHandler
// cannot use specificHandler as GenericHandler value

Fix with adapters:

var g GenericHandler = func(v interface{}) {
    specificHandler(v.(string))
}

Fix 7: Fix Map Type Mismatches

Like slices, maps are not covariant:

func process(m map[string]interface{}) {}

data := map[string]int{"a": 1, "b": 2}
process(data)
// cannot use data as map[string]interface{} value

Fixed — use generics:

func process[V any](m map[string]V) {}

data := map[string]int{"a": 1, "b": 2}
process(data)  // Works!

Fixed — convert manually:

converted := make(map[string]interface{}, len(data))
for k, v := range data {
    converted[k] = v
}
process(converted)

Fix 8: Fix Error Interface Returns

A common confusion with the error interface:

type MyError struct {
    Message string
}

func (e *MyError) Error() string {
    return e.Message
}

func doSomething() error {
    var err *MyError = nil
    return err  // Returns a non-nil interface containing a nil pointer!
}

if err := doSomething(); err != nil {
    // This is TRUE even though the underlying pointer is nil!
    fmt.Println("Error:", err)
}

Fixed — return nil directly:

func doSomething() error {
    // ...
    return nil  // Return nil, not a typed nil pointer
}

This is a notorious Go gotcha. An interface is only nil when both its type and value are nil. A nil *MyError stored in an error interface has a non-nil type, so err != nil is true.

For related Go issues with unused variables, see Fix: Go declared and not used.

Still Not Working?

Check for generic constraints. In Go 1.18+, generic type parameters must satisfy their constraints:

func sum[T int | float64](values []T) T { ... }

sum([]string{"a", "b"})  // string does not satisfy int | float64

Check for type assertions. If you need to work with interface{} values, use type assertions:

var v interface{} = "hello"
s := v.(string)   // Type assertion — panics if wrong type
s, ok := v.(string)  // Safe assertion — ok is false if wrong type

Check for struct embedding. Embedded structs can satisfy interfaces through promoted methods, but only if the method signatures match exactly.

Use the compiler’s suggestion. Go’s error messages often include the expected and actual types. Compare them carefully — the difference might be subtle (pointer vs value, named vs unnamed type, different package paths for the same type name).

Check for vendored vs module-resolved types. If your project uses both vendor/ and GOFLAGS=-mod=mod, you can end up with two copies of the same type from the same package. The compiler sees them as different types even though their fully qualified names look identical. Either commit fully to vendoring (GOFLAGS=-mod=vendor) or remove vendor/ entirely.

Check for generic type inference failures. With Go 1.21+, the compiler infers type parameters from arguments, but inference fails when the argument is itself a generic call or when a constraint cannot be unified. Pass type arguments explicitly — MyFunc[int](args) — to verify whether the issue is inference or the underlying constraint.

Check for nil pointer issues that masquerade as type errors. A common variant is returning (*MyError)(nil) from a function that returns error. The caller’s if err != nil will be true because the interface holds a typed nil. See Fix: Go nil pointer dereference for the broader nil-handling pattern and Fix: Go interface nil panic for the interface-specific case.

For Go module issues that prevent compilation, see Fix: Go module not found.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles