Go

Go interfaces are satisfied implicitly. What design consequences follow, and where should interfaces be defined?

Question 94MediumGo 1.22 to 1.25

Because satisfaction is structural and there is no implements keyword, a type can satisfy an interface that was written after it, in a package it has never heard of. The idiomatic consequence is to define interfaces at the consumer, not next to the producer.

// package storage (producer): exports a concrete type
type PostgresStore struct{ db *sql.DB }
func (s *PostgresStore) GetUser(ctx context.Context, id int64) (User, error) { /*...*/ }
func (s *PostgresStore) SaveUser(ctx context.Context, u User) error         { /*...*/ }
// ... 20 more methods

// package billing (consumer): declares only what it needs
type userGetter interface {
    GetUser(ctx context.Context, id int64) (User, error)
}

type Service struct{ users userGetter }

// In tests: a 3-line fake, no mocking framework required
type fakeUsers struct{ u User }
func (f fakeUsers) GetUser(context.Context, int64) (User, error) { return f.u, nil }

Benefits:

  • The producer package does not import the consumer, so the two stay decoupled.
  • Each consumer declares a minimal interface.
  • Fakes for tests are trivial to write.
  • No large "IUserRepository" interfaces that mirror a single implementation.

Exceptions: when you ship several implementations of a shared abstraction (io.Reader, hash.Hash, sql/driver), define the interface in the producer or in a shared package.

Red flag for the interviewer: creating an interface before a second implementation or a test double exists.

More on Interfaces, Methods & Embedding

All 35 Interfaces, Methods & Embedding questions