Use interfaces when you need polymorphism, dependency injection, or to decouple code from specific implementations; use concrete types when performance matters or the implementation is fixed. Interfaces add a small runtime cost for dynamic dispatch, while concrete types allow the compiler to inline methods and optimize aggressively.
// Use an interface for flexibility
func Process(r io.Reader) error {
// Works with bytes.Reader, os.File, net.Conn, etc.
_, err := io.ReadAll(r)
return err
}
// Use a concrete type for performance
func FastProcess(b []byte) error {
// Compiler inlines directly; no interface overhead
_ = len(b)
return nil
}