Accepting interfaces in function parameters makes a function more useful for the function caller. Any type which properly implements the interface can be used as the function argument. The requirements for the argument type are clearly defined. In several languages, generics can remove the performance cost of accepting an interface versus a concrete type. Interfaces are good.

However, returning interfaces from a function is bad. Ignoring potential performance issues, if a function declares it is returning only an interface type, then the function only guarantees returning a type which implements that interface. The caller can only call those declared interface methods. So far, this is fine.

But what if the returned API needs to be expanded? For instance, the function author decided that there is a useful helper method which should be added to the return type. Unfortunately, it may be difficult to add a method to the interface. For instance, if other consumers of the function decided that they wanted to mock/stub the return value from the function by implementing the interface with their own type for tests. Adding the method would break all of their code.

With a concrete type, in the majority of situations, a new method can be added easily while maintaining comparability. The new method could even be called from the concrete type’s existing methods.

So try to accept interfaces but return concrete types for maximum flexibility.