How to check if value implements interface

Use a type assertion or a type switch to check if a value implements an interface at runtime.

When you need to ask a value what it can do

You are building a generic event handler. It receives a stream of values wrapped in any. Most of the time, you just log the raw data. But sometimes you need to extract a specific method, like String(), to format the output for a dashboard. You cannot call v.String() directly because the compiler sees v as any and refuses to guess. You need a way to ask the runtime: "Does this value speak the Stringer language?"

Go gives you two tools for this job. The two-value type assertion checks for a single interface safely. The type switch handles multiple possibilities in one block. Both rely on the fact that Go stores type information inside interface values, even when the static type is any.

Interfaces are contracts, not declarations

Go interfaces are implicit. You do not declare that a struct implements an interface. You simply write the methods. If a struct has a String() string method, it implements fmt.Stringer. The compiler enforces this at compile time for concrete types. When you pass a concrete value to a function expecting an interface, the compiler verifies the method set matches.

At runtime, the story changes slightly. When a value is stored in an any variable, the static type information is hidden. The variable holds an interface value, which is a pair: a pointer to the concrete type's descriptor and a pointer to the data. The descriptor contains the method set. Checking if a value implements an interface is just comparing the stored method set against the interface's requirements.

This runtime representation means you can recover type information. You can ask if the stored type has the methods you need. The check happens in nanoseconds. It is a comparison of type descriptors, not a reflection scan.

The safe check: two-value assertion

The standard way to check for a specific interface is the two-value type assertion. It returns the value cast to the interface type and a boolean indicating success. The boolean prevents panics when the type does not match.

Here is the pattern for checking if a value implements fmt.Stringer.

func logEvent(v any) {
    // v is any, so the compiler hides the concrete type.
    // Two-value form returns the cast value and a bool.
    // ok is true only if v has the String method.
    if s, ok := v.(fmt.Stringer); ok {
        // s is now fmt.Stringer.
        // Safe to call String() here.
        fmt.Println("Event:", s.String())
    } else {
        // Handle the case where the interface is missing.
        // v does not implement Stringer.
        fmt.Println("Raw event:", v)
    }
}

The two-value form is the default choice. It handles uncertainty gracefully. The single-value form v.(T) panics if the assertion fails. You only use the single-value form when you are certain the type matches and a panic is the correct response to a programming error.

The two-value assertion is safe. The single-value form is a promise that crashes if broken.

What happens at runtime

When you write v.(T), the runtime inspects the interface value stored in v. It looks at the type descriptor to see if the concrete type has all the methods required by T. If the methods exist, the assertion succeeds. The result is the value wrapped in the new interface type T. If the methods are missing, the assertion fails.

For the two-value form, failure returns the zero value of T and false. For the single-value form, failure triggers a panic with a message like interface conversion: main.MyType does not implement fmt.Stringer (missing String method).

The check is fast because it compares type descriptors. It does not allocate memory. It does not iterate over methods. The compiler generates a direct comparison of the type information stored in the interface. This efficiency makes type assertions suitable for hot paths, though you should still prefer static typing when possible.

Handling multiple types with a switch

When you face multiple possibilities, a type switch replaces a chain of if-else assertions. It extracts the value and checks the type in one step. The syntax is cleaner, and the compiler can verify you covered all cases if the type is an exhaustive interface.

Here is a handler that processes different config values based on their capabilities.

func processConfig(v any) {
    // Type switch extracts the value and checks type in one step.
    // val has the specific type in each case.
    // No need for repeated assertions.
    switch val := v.(type) {
    case fmt.Stringer:
        // val is fmt.Stringer here.
        // Call methods defined by the interface.
        fmt.Println("Stringer:", val.String())
    case io.Reader:
        // val is io.Reader here.
        // Handle reading logic.
        fmt.Println("Reader detected")
    case int:
        // val is int here.
        // Handle numeric logic.
        fmt.Println("Integer:", val)
    default:
        // Default catches types that match nothing.
        // val is still any in the default case.
        fmt.Println("Unknown type")
    }
}

The type switch is idiomatic for dispatching on type. It avoids nested if statements and makes the control flow obvious. Each case binds the value to a variable with the specific type, so you can use methods or fields without further casting.

Type switches are readable. Chains of assertions are not.

The pointer receiver trap

A common pitfall involves pointer receivers. If a method has a pointer receiver, the value type does not implement the interface. Only the pointer type does. This distinction matters for assertions.

Consider a struct with a String method on a pointer receiver.

type Sensor struct {
    ID string
}

// String has a pointer receiver.
// Only *Sensor implements fmt.Stringer.
// Sensor does not implement fmt.Stringer.
func (s *Sensor) String() string {
    return "Sensor:" + s.ID
}

func checkSensor(v any) {
    // v holds a Sensor value, not a pointer.
    // Assertion fails because Sensor lacks the method.
    // ok is false.
    if _, ok := v.(fmt.Stringer); !ok {
        // This branch executes.
        // The value type does not satisfy the interface.
        fmt.Println("Value does not implement Stringer")
    }
}

If you pass a Sensor value to checkSensor, the assertion fails. The runtime sees that Sensor does not have the String method. The method exists on *Sensor, but the value is not a pointer. The two-value form returns false. The single-value form panics with interface conversion: main.Sensor does not implement fmt.Stringer (Sensor.String has pointer receiver).

To fix this, pass a pointer or check for the pointer type. If you control the method, consider whether a value receiver is sufficient. Value receivers allow both values and pointers to implement the interface. Pointer receivers restrict implementation to pointers.

The compiler error for pointer receivers is explicit. Read it carefully. It tells you exactly why the method set does not match.

Convention: accept interfaces, return structs

Go style prefers static typing. The mantra is "accept interfaces, return structs." If a function needs behavior, accept the interface as a parameter. Do not take any and check for the interface at runtime.

Runtime checks are for when the type truly varies, like in a plugin system, a generic serializer, or a library that must handle unknown user data. For application code, passing any and asserting hides requirements from the caller. The compiler cannot help you. You lose type safety and readability.

Here is the preferred pattern. The function signature enforces the contract.

// requireStringer takes fmt.Stringer.
// The compiler checks the argument at the call site.
// No runtime check needed.
func requireStringer(s fmt.Stringer) {
    // s is guaranteed to have String().
    // Call methods directly.
    fmt.Println(s.String())
}

If you write requireStringer(myValue), the compiler rejects the call if myValue does not implement fmt.Stringer. This is better than a runtime panic or a silent failure. It fails fast, at compile time, with a clear error message.

Use runtime checks sparingly. Reserve them for cases where the type is genuinely dynamic. For everything else, let the compiler do the work.

Don't check for interfaces at runtime if the compiler can enforce them at the call site.

Decision matrix

Use a function parameter with the interface type when the caller must satisfy a contract and you want compile-time safety.

Use a two-value type assertion when you have an any value and need to check for one specific interface without panicking.

Use a type switch when you need to handle multiple distinct types or interfaces from a single any value.

Use a single-value type assertion only when you are certain the type matches and a panic is the desired behavior for a programming error.

Use reflection (reflect.TypeOf) only when you need to inspect the type name or methods dynamically, which is rare and slower than assertions.

Where to go next