Interfaces in Go are one of the language's most powerful features, yet they work differently than in most object-oriented languages. Instead of explicit declarations, Go uses implicit interface satisfaction.
The Basics
An interface in Go is simply a set of method signatures. Any type that implements all those methods automatically satisfies the interface - no implements keyword required.
type Writer interface {
Write(p []byte) (n int, err error)
}
type ConsoleWriter struct{}
func (cw ConsoleWriter) Write(p []byte) (n int, err error) {
return fmt.Println(string(p))
}
The ConsoleWriter type now satisfies the Writer interface without any explicit declaration.
Why This Matters
This implicit satisfaction enables a style of programming where you can:
- Define interfaces where they're used, not where types are defined
- Create small, focused interfaces (often with just one method)
- Easily mock dependencies for testing
- Decouple packages without import dependencies
The Empty Interface
The empty interface interface{} (or any in Go 1.18+) is satisfied by every type. Use it sparingly - it bypasses the type system.
"The bigger the interface, the weaker the abstraction." - Rob Pike
Practical Example
Here's how you might use interfaces to make code testable:
// Define interface where it's needed
type UserStore interface {
GetUser(id string) (*User, error)
}
// Service depends on interface, not concrete type
type AuthService struct {
users UserStore
}
func (s *AuthService) Authenticate(id, password string) bool {
user, err := s.users.GetUser(id)
if err != nil {
return false
}
return user.CheckPassword(password)
}
Now you can easily swap implementations for testing or different storage backends.
Conclusion
Go interfaces encourage composition over inheritance and lead to more flexible, testable code. Start with concrete types, extract interfaces when you need abstraction, and keep them small.