An interface is a set of method signatures. Any type that has all those methods satisfies the interface — no declaration needed. The compiler checks this. This is called structural typing.
Implicit vs explicit implementation
☕ Java — explicit implements
// Interface definedpublic interface MessageStore { Message getById(long id); void save(Message m);}// Class MUST declare implementspublic class PostgresStore implements MessageStore { @Override public Message getById(long id) { ... } @Override public void save(Message m) { ... }}
◎ Go — implicit satisfaction
// Interface definedtype MessageStore interface { GetByID(id int64) (Message, error) Save(m Message) error}// Implementation — NO 'implements' keyword// Just... have the methods. That is it.type pgStore struct{ db *sql.DB }func (s pgStore) GetByID(id int64) (Message, error) { // ... query postgres ... return Message{}, nil}func (s pgStore) Save(m Message) error { // ... insert ... return nil}// pgStore satisfies MessageStore — compiler verifiesvar _ MessageStore = pgStore{} // compile-time check
Why Go does thisImplicit implementation means a type can satisfy an interface it has never heard of. You can define an interface in your package for a type that lives in a library you do not control. This is the opposite of Java, where the implementer must know about every interface it satisfies at the time of writing.
Define interfaces where they are USED, not implemented
go — interface at call site pattern
// handler/handler.go — the CONSUMER defines what it needspackage handler// This interface lives in the handler package, not the store package// The handler only declares the methods IT actually callstype messageReader interface { GetByID(id int64) (Message, error) // only need to read}type Handler struct { store messageReader // depends on the interface, not pgStore}// Now pgStore satisfies messageReader automatically// (pgStore.GetByID exists and matches the signature)// pgStore never needs to change, never needs to know about Handler// This is the opposite of Java where the interface usually// lives next to the implementation, not the consumer
Interface composition
composing small interfaces into larger ones
// Good Go style: define many small focused interfacestype Reader interface { GetByID(id int64) (Message, error)}type Writer interface { Save(m Message) error Delete(id int64) error}// Compose them when a function needs bothtype ReadWriter interface { Reader // embed Reader interface Writer // embed Writer interface}// Function that only reads — accept Reader, not ReadWriter// This makes testing easy: mock only has to implement one methodfunc GetHandler(store Reader) http.HandlerFunc { ... }// Standard library interfaces you will see everywhere:// io.Reader -> Read(p []byte) (n int, err error)// io.Writer -> Write(p []byte) (n int, err error)// io.Closer -> Close() error// io.ReadWriteCloser composes all three
→Keep interfaces small. 1–3 methods is ideal. A function that accepts a 10-method interface is hard to mock and hard to swap. A function that accepts a 1-method interface can work with anything.