How to Design Good Interfaces in Go (Accept Interfaces, Return Structs)

Accept interfaces as function arguments to allow flexibility and return concrete structs as results to ensure clarity and testability in Go code.

Design Go interfaces by accepting them as function parameters and returning concrete structs as results to maximize flexibility and minimize coupling. Define an interface that describes only the behavior needed by the caller, then implement that interface with a struct that holds the actual state.

// Define interface with only required methods
type Reader interface {
    Read(p []byte) (n int, err error)
}

// Function accepts interface (flexible input)
func Process(r Reader) error {
    var buf [1024]byte
    _, err := r.Read(buf[:])
    return err
}

// Function returns concrete struct (explicit output)
func NewFileReader(name string) *FileReader {
    return &FileReader{name: name}
}

type FileReader struct {
    name string
}

func (f *FileReader) Read(p []byte) (int, error) {
    // Implementation details
    return 0, nil
}