Question

Go Interfaces vs Pointers to Interfaces: Why *Interface Is Usually Wrong

gopointersinterface

Question

Why does Go reject a *FieldFilter value when a method expects *FilterInterface?

package coreinterfaces

type FilterInterface interface {
    Filter(s *string) bool
}

type FieldFilter struct {
    Key string
    Val string
}

func (ff *FieldFilter) Filter(s *string) bool {
    // Some code
    return true
}

type FilterMapInterface interface {
    AddFilter(f *FilterInterface) uuid.UUID
    RemoveFilter(i uuid.UUID)
    GetFilterByID(i uuid.UUID) *FilterInterface
}

type FilterMap struct {
    mutex   sync.Mutex
    Filters map[uuid.UUID]FilterInterface
}

func (fp *FilterMap) AddFilter(f *FilterInterface) uuid.UUID {
    // Some code
    return uuid.Nil
}

The following call produces this compiler error:

func DoFilter() {
    fieldfilter := &coreinterfaces.FieldFilter{Key: "app", Val: "152511"}
    filtermap := &coreinterfaces.FilterMap{}
    _ = filtermap.AddFilter(fieldfilter)
}

cannot use fieldfilter (type *coreinterfaces.FieldFilter) as type *coreinterfaces.FilterInterface in argument to filtermap.AddFilter: *coreinterfaces.FilterInterface is pointer to interface, not interface

Why does assigning fieldfilter to an intermediate FilterInterface variable and then passing &ifilterfield work? What is the idiomatic way to design this API without creating that intermediate variable?

Short Answer

You will learn the difference between an interface value and a pointer to an interface in Go. In almost all API designs, parameters, returns, fields, and map values should use FilterInterface, not *FilterInterface. A *FieldFilter can be stored directly in a FilterInterface because it implements that interface.

Concept

An interface in Go is a value that describes behavior. It can hold a concrete value plus the information Go needs to call that value's methods.

For this type:

type FilterInterface interface {
    Filter(s *string) bool
}

*FieldFilter implements FilterInterface because its method has a pointer receiver:

func (ff *FieldFilter) Filter(s *string) bool

Therefore, this assignment is valid:

var f FilterInterface
f = &FieldFilter{Key: "app", Val: "152511"}

However, *FilterInterface means something different: it is a pointer whose target is an interface variable. It is not an interface that accepts pointers to implementations.

These are different types:

var implementation *FieldFilter
var behavior FilterInterface
var pointerToBehavior *FilterInterface

A *FieldFilter cannot be used where *FilterInterface is required. Go does not automatically create an interface variable, place the concrete value in it, and then return the address of that variable. Your intermediate-variable version works precisely because it explicitly performs all of those steps.

This is a compile-time type error, not a runtime exception. The compiler prevents the invalid call before the program runs.

Interfaces are commonly used to decouple code: a FilterMap should depend on the FilterInterface behavior, not on a particular filter implementation. Since an interface can already contain a pointer such as *FieldFilter, adding another pointer level is usually unnecessary.

Mental Model

Think of an interface value as a labeled package.

  • The label says what operations are available: Filter.
  • Inside the package is the actual value, such as *FieldFilter.
var f FilterInterface = &FieldFilter{}

Here, f is the package, and it contains a pointer to a FieldFilter.

A *FilterInterface is not another kind of package for filters. It is only a street address pointing to the variable that holds the package:

var f FilterInterface = &FieldFilter{}
var p *FilterInterface = &f

Most functions need the package so they can call Filter. They do not need its address. Use a pointer to an interface only in rare cases where the function must replace the caller's entire interface value.

Take Quiz

Syntax and Examples

Use the interface type directly in the API. The concrete implementation may still be a pointer.

package coreinterfaces

import (
    "sync"

    "github.com/google/uuid"
)

type FilterInterface interface {
    Filter(s *string) bool
}

type FieldFilter struct {
    Key string
    Val string
}

func (ff *FieldFilter) Filter(s *string) bool {
    return s != nil && *s == ff.Val
}

type FilterMapInterface interface {
    AddFilter(f FilterInterface) uuid.UUID
    RemoveFilter(id uuid.UUID)
    GetFilterByID(id uuid.UUID) (FilterInterface, bool)
}

type FilterMap struct {
    mutex   sync.Mutex
    Filters map[uuid.UUID]FilterInterface
}

func (fm *FilterMap) AddFilter(f FilterInterface) uuid.UUID {
    fm.mutex.Lock()
    defer fm.mutex.Unlock()

    if fm.Filters == nil {
        fm.Filters = make(map[uuid.UUID]FilterInterface)
    }

    id := uuid.New()
    fm.Filters[id] = f
    return id
}

func (fm *FilterMap) GetFilterByID(id uuid.UUID) (FilterInterface, bool) {
    fm.mutex.Lock()
    defer fm.mutex.Unlock()

    filter, ok := fm.Filters[id]
    return filter, ok
}

Call it without an intermediate interface variable:

filterMap := &coreinterfaces.FilterMap{}
fieldFilter := &coreinterfaces.FieldFilter{Key: "app", Val: "152511"}

id := filterMap.AddFilter(fieldFilter)

The call works because fieldFilter has type *FieldFilter, and *FieldFilter satisfies FilterInterface.

Returning (FilterInterface, bool) follows Go's usual map-lookup style. It distinguishes a missing ID from an ID whose stored interface value might be nil.

Step by Step Execution

Consider this short example:

type Filter interface {
    Filter(*string) bool
}

type FieldFilter struct{}

func (*FieldFilter) Filter(*string) bool {
    return true
}

func add(f Filter) {
    value := "hello"
    fmt.Println(f.Filter(&value))
}

func main() {
    fieldFilter := &FieldFilter{}
    add(fieldFilter)
}

Step by step:

  1. fieldFilter := &FieldFilter{} creates a FieldFilter and produces a value of type *FieldFilter.
  2. Go checks whether *FieldFilter implements Filter.
  3. It does, because *FieldFilter has the method Filter(*string) bool.
  4. add expects a Filter, so Go creates an interface value containing the *FieldFilter value.
  5. Inside add, f.Filter(&value) dispatches to (*FieldFilter).Filter.

Now compare an incorrect signature:

func addWrong(f *Filter) {}

addWrong requires the address of an existing variable whose type is Filter:

var f Filter = &FieldFilter{}
addWrong(&f)

That is why the intermediate-variable version compiles. It creates an interface variable named f, then passes its address. It is usually extra indirection with no benefit.

Real World Use Cases

Interfaces are useful when a component needs behavior but should not depend on one concrete type.

  • Validation pipelines: Store multiple FilterInterface implementations, such as field filters, regular-expression filters, and permission filters.
  • HTTP services: Accept an interface for a user repository so production code can use a database while tests use an in-memory fake.
  • Logging: Accept a logging interface so an application can write to the console, a file, or a remote log service.
  • Payment providers: Depend on a payment interface so a checkout service can support different providers.
  • File and network operations: Go's standard library uses interfaces such as io.Reader and io.Writer; implementations are often pointers to structs.

For example, io.Reader is passed as an interface value, not as *io.Reader:

func readAll(r io.Reader) ([]byte, error) {
    return io.ReadAll(r)
}
Take Quiz

Real Codebase Usage

In real Go code, follow these patterns.

Accept interfaces, return concrete types when practical

A function that only needs filtering behavior can accept the small interface:

func ApplyFilter(f FilterInterface, input *string) bool {
    return f.Filter(input)
}

A constructor commonly returns a concrete pointer because callers may need the concrete configuration fields:

func NewFieldFilter(key, value string) *FieldFilter {
    return &FieldFilter{Key: key, Val: value}
}

Use guard clauses for invalid interface values

An interface may be nil, so validate required dependencies at boundaries:

func (fm *FilterMap) AddFilter(f FilterInterface) (uuid.UUID, error) {
    if f == nil {
        return uuid.Nil, errors.New("filter cannot be nil")
    }

    // Store f...
    return uuid.New(), nil
}

Initialize maps before assignment

A nil map can be read but cannot receive assignments. Initialize it in a constructor or lazily in AddFilter:

func NewFilterMap() *FilterMap {
    return &FilterMap{
        Filters: make(map[uuid.UUID]FilterInterface),
    }
}

Keep interfaces small and consumer-focused

If only Filter is needed, define only Filter. Avoid adding unrelated operations merely because multiple implementations happen to provide them.

Common Mistakes

Declaring a pointer to an interface

Incorrect for this API:

func (fm *FilterMap) AddFilter(f *FilterInterface) uuid.UUID

Correct:

func (fm *FilterMap) AddFilter(f FilterInterface) uuid.UUID

An interface can already hold *FieldFilter.

Assuming *ConcreteType is *InterfaceType

This does not compile:

var fieldFilter *FieldFilter
var filter *FilterInterface = fieldFilter // invalid

Implementing an interface allows assignment to the interface, not to a pointer to that interface:

var filter FilterInterface = fieldFilter // valid if fieldFilter is non-nil

Forgetting method receiver rules

Because Filter has a pointer receiver, only *FieldFilter implements the interface:

var f FilterInterface
f = FieldFilter{}  // invalid
f = &FieldFilter{} // valid

Use a value receiver only when copying the value is appropriate and the method does not need to modify it.

Assigning to a nil map

This panics:

fm.Filters[id] = f // panic if fm.Filters is nil

Initialize Filters with make(map[uuid.UUID]FilterInterface) first.

Confusing a nil interface with an interface containing a nil pointer

var fieldFilter *FieldFilter = nil
var f FilterInterface = fieldFilter

fmt.Println(f == nil) // false

f is non-nil because it contains type information (*FieldFilter), even though its contained pointer is nil. Be deliberate about nil checks and avoid calling methods that dereference a nil receiver.

Comparisons

Type or patternWhat it representsTypical use
FilterInterfaceAn interface value containing any implementation of FilterInterfaceFunction parameters, struct fields, map values
*FieldFilterA pointer to a concrete FieldFilter structCreating and configuring a field filter; satisfies FilterInterface
*FilterInterfaceA pointer to a variable that contains an interface valueRare cases where a function must replace the caller's interface variable
FieldFilterA concrete value, not a pointerSatisfies the interface only if methods use value receivers

Interface value vs pointer to interface

func use(f FilterInterface)   {} // Normal and idiomatic
func replace(f *FilterInterface) {} // Rare; can assign a new interface value to *f

A pointer to an interface may be appropriate when decoding or mutating an interface variable itself, but it is not the normal way to support multiple implementations.

Pointer receiver vs value receiver

ReceiverImplements the interface asUse when
func (x Thing) Method()Thing and *ThingThe type is small and copying is safe
func (x *Thing) Method()*Thing onlyThe method modifies state, avoids copying, or needs pointer semantics
Take Quiz

Cheat Sheet

// Define behavior
type Filter interface {
    Filter(*string) bool
}

// Implement it with a pointer receiver
type FieldFilter struct{}
func (*FieldFilter) Filter(*string) bool { return true }

// Accept the interface, not *Filter
func AddFilter(f Filter) {}

// Pass a concrete pointer directly
AddFilter(&FieldFilter{})
  • Interface means an interface value.
  • *Interface means a pointer to an interface variable.
  • A *ConcreteType can be assigned to Interface when it implements the interface.
  • A *ConcreteType is not assignable to *Interface.
  • Interfaces can contain pointers already; do not add * merely to avoid copying.
  • Pointer-receiver methods mean only the pointer type implements the interface.
  • Initialize a map before writing: make(map[Key]Filter).
  • Prefer (value, ok) when looking up a value in a map.

FAQ

Why are pointers to interfaces usually unnecessary in Go?

An interface value already stores a reference-like description of a concrete value and can contain a pointer to a struct. Passing the interface value is normally enough to call methods and share the underlying object.

Does *FieldFilter implement FilterInterface?

Yes. Filter is declared with receiver *FieldFilter, so *FieldFilter implements FilterInterface.

Does FieldFilter implement FilterInterface too?

No. With a pointer receiver, the method set belongs to *FieldFilter, not FieldFilter.

Why does the intermediate interface variable compile?

It creates a FilterInterface variable containing *FieldFilter, then passes the address of that interface variable. This matches *FilterInterface, but it is usually not the API you want.

Should a map store FilterInterface or *FilterInterface?

Store FilterInterface in almost all cases. Each stored interface can hold a *FieldFilter or another implementation.

Can an interface hold a nil pointer?

Yes. An interface containing a typed nil pointer is not itself nil. This can cause surprising nil checks, so validate values carefully.

When should I use a pointer to an interface?

Only when you specifically need to modify which concrete value an existing interface variable contains. This is uncommon in ordinary application APIs.

Take Quiz

Mini Project

Description

Build a small in-memory filter registry. It stores different filter implementations behind one interface, assigns each filter a UUID, and retrieves filters safely. The project demonstrates that the registry stores interface values while concrete filters can be pointers.

Goal

Create a registry that accepts *FieldFilter directly as a Filter and applies a stored filter to text.

Requirements

Create a Filter interface with a Filter(*string) bool method. Create a FieldFilter implementation with a pointer receiver. Store filters in a map[uuid.UUID]Filter. Initialize the map before inserting filters. Return an existence flag when retrieving a filter. Pass &FieldFilter{...} directly to AddFilter.

Take Quiz

Keep learning

Related questions

View more