Go

Explain "accept interfaces, return structs". When is returning an interface justified?

Question 95MediumGo 1.22 to 1.25

Accept interfaces: parameters should ask for the smallest behaviour needed (io.Reader rather than *os.File). Callers can then pass files, buffers, network connections or test fakes.

Return structs: constructors should return the concrete type (*Client). Callers get the full API, can reach fields and extra methods, can define their own narrower interfaces, and you can add methods later without breaking anyone. Adding a method to an exported interface is a breaking change for everyone who implements it.

// Good
func NewParser(r io.Reader) *Parser { return &Parser{r: bufio.NewReader(r)} }

// Usually bad: hides the concrete type, forces the interface on every caller,
// and invites the typed-nil bug
func NewParserBad(r io.Reader) ParserInterface { ... }

Returning an interface is justified when:

  • The function can return several concrete types: errors.New returns error, io.MultiReader returns io.Reader, crypto/sha256.New returns hash.Hash.
  • The concrete type is deliberately unexported, so the implementation can change freely (context.Context).
  • The result is a factory or plugin chosen at runtime.

The guideline is a default, not a law. Interviewers want to hear the reasoning (API evolution, caller freedom, avoiding typed nils) rather than the slogan.

More on Interfaces, Methods & Embedding

All 35 Interfaces, Methods & Embedding questions