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.FilterInterfacein argument tofiltermap.AddFilter:*coreinterfaces.FilterInterfaceis 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.
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:
fieldFilter := &FieldFilter{}creates aFieldFilterand produces a value of type*FieldFilter.- Go checks whether
*FieldFilterimplementsFilter. - It does, because
*FieldFilterhas the methodFilter(*string) bool. addexpects aFilter, so Go creates an interface value containing the*FieldFiltervalue.- 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
FilterInterfaceimplementations, 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.Readerandio.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)
}
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 pattern | What it represents | Typical use |
|---|---|---|
FilterInterface | An interface value containing any implementation of FilterInterface | Function parameters, struct fields, map values |
*FieldFilter | A pointer to a concrete FieldFilter struct | Creating and configuring a field filter; satisfies FilterInterface |
*FilterInterface | A pointer to a variable that contains an interface value | Rare cases where a function must replace the caller's interface variable |
FieldFilter | A concrete value, not a pointer | Satisfies 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
| Receiver | Implements the interface as | Use when |
|---|---|---|
func (x Thing) Method() | Thing and *Thing | The type is small and copying is safe |
func (x *Thing) Method() | *Thing only | The method modifies state, avoids copying, or needs pointer semantics |
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{})
Interfacemeans an interface value.*Interfacemeans a pointer to an interface variable.- A
*ConcreteTypecan be assigned toInterfacewhen it implements the interface. - A
*ConcreteTypeis 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.
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.
Keep learning
Related questions
Are Go Maps Passed by Value or Reference?
Learn Go map parameter and return semantics: maps are passed by value, while copies share underlying data. Know when map pointers are useful.
Authentication for Go REST APIs: Sessions, Tokens, and Middleware
Learn practical authentication patterns for Go REST APIs, including password hashing, server sessions, bearer tokens, middleware, and security rules.
Automatic Build Versioning in Go: Embed Incrementing Build Numbers
Learn how to add automatic build versioning in Go using linker flags, build metadata, CI counters, and Git-based version values.