Defining and Implementing Interfaces
Go interfaces describe behavior through method sets satisfied implicitly by concrete types.
Search across all documentation pages
Go interfaces describe behavior through method sets satisfied implicitly by concrete types.
Define small interfaces at the consumer, accept them in function parameters, and return concrete structs from constructors.
An interface value holds a dynamic type and dynamic value pair.
Satisfaction is compile-time checked when you assign; no implements keyword exists.
Small interfaces (often one or two methods) keep mocks thin and encourage composition over wide abstractions.
Accept interfaces, return structs is the headline API rule: depend on minimal behavior in parameters; expose concrete types from New functions so callers are not trapped behind your interface definitions.
Avoid exporting large interfaces from producer packages - they become painful compatibility contracts.
Quick-reference recipe card - copy-paste ready.
// Consumer package defines what it needs.
type Storage interface {
Get(ctx context.Context, key string) ([]byte, error)
Put(ctx context.Context, key string, value []byte) error
}
// Producer returns concrete type.
type MemStore struct {
mu sync.RWMutex
m map[string][]byte
}
func NewMemStore() *MemStore {
return &MemStore{m: make(map[string][]byte)}
}
func (s *MemStore) Get(ctx context.Context, key string) ([]byte, error) { /* ... */ }
func (s *MemStore) Put(ctx context.Context, key string, value []byte) error { /* ... */ }
func LoadConfig(ctx context.Context, store Storage, key string) ([]byte, error) {
return store.Get(ctx, key)
}When to reach for this:
io.Reader, io.Writer).Handler, gRPC ServerStream).package notify
import (
"context"
"fmt"
)
type Sender interface {
Send(ctx context.Context, to, body string) error
}
type Service struct {
sender Sender
}
func New(sender Sender) *Service {
return &Service{sender: sender}
}
type LogSender struct{}
func (LogSender) Send(ctx context.Context, to, body string) error {
fmt.Printf("to=%s body=%q\n", to, body)
return nil
}
type SMTPClient struct {
host string
}
func NewSMTP(host string) *SMTPClient {
return &SMTPClient{host: host}
}
func (c *SMTPClient) Send(ctx context.Context, to, body string) error {
// dial c.host, send message ...
return nil
}
func (s *Service) Welcome(ctx context.Context, email string) error {
return s.sender.Send(ctx, email, "welcome")
}What this demonstrates:
Sender interface lives with consumer Service, not SMTP package.New accepts Sender for injection; returns *Service concrete type.M is satisfied by any type whose method set includes M.any holds all types; use only at decoding boundaries.io.ReadCloser style compositions.| Rule | Rationale |
|---|---|
| Define at consumer | Producers stay independent |
| Keep 1-3 methods | Small fakes, easier evolution |
| Name by capability | Storage, Sender, not IStorage |
Return structs from New | Callers not forced to import interface types |
| Document nil behavior | Especially for returned interfaces |
| Interface | Methods | Typical implementers |
|---|---|---|
io.Reader | Read | Files, buffers, network |
fmt.Stringer | String | Domain types for logging |
error | Error | Sentinels, wrapped errors |
http.Handler | ServeHTTP | Routers, chi/gin/echo handlers |
// Split interfaces when methods are unrelated.
type Reader interface { Read(p []byte) (int, error) }
type Writer interface { Write(p []byte) (int, error) }
type ReadWriter interface {
Reader
Writer
}Prefer embedding to bloated single interfaces.
*Concrete unless abstraction is the product.== nil. Fix: return concrete types or document checks with reflection/errors.Is patterns.| Alternative | Use When | Don't Use When |
|---|---|---|
| Function parameter | Single hook (Validator func() error) | Multiple coordinated methods |
| Generics constraint | Shared algorithms over types | Runtime plugin swapping |
| Concrete type only | Single implementation forever | Need tests with fakes |
any + type switch | JSON plugin registry | Stable domain APIs |
Declare methods with matching names and signatures.
The compiler verifies at assignment - no explicit declaration.
At the package that uses the behavior, not the package that provides implementations.
Exception: standard library idioms like io.Reader define shared contracts.
Often one method (Reader, Writer, Stringer).
Add methods only when callers truly need them together.
No - only methods.
Share data through concrete structs passed alongside interfaces or returned values.
Both type and value slots are unset.
Different from an interface containing a typed nil pointer.
In application packages, yes - for test doubles.
In simple CLIs with one DB, a concrete type may suffice until tests demand fakes.
Use generated interfaces or narrow wrappers around specific RPC methods.
Avoid mocking entire generated client structs when one method matters.
It is a breaking change for external implementers.
Introduce a new interface name or unexported interface inside your package.
errors.As checks whether an error implements an interface and extracts it.
Define sentinel interfaces for classification sparingly.
Prefer http.Handler interfaces for portability across chi, gin, echo adapters.
Framework types leak when used as domain boundaries.
Unexported methods on interfaces restrict implementation to your package.
Useful for sealing implementations while exporting the interface type.
Linters like iface detect unused interfaces and suggest removal.
ireturn may flag functions returning interfaces - align with team style.
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 18, 2026