Go Interfaces: The Power of Implicit Implementation
Understand Go interfaces, implicit implementation, interface composition, and common patterns like io.Reader.
What Makes Go Interfaces Special?
In Go, interfaces are implemented implicitly. A type satisfies an interface simply by implementing all its methods — no implements keyword needed.
``go
type Speaker interface {
Speak() string
}
type Dog struct{ Name string } type Cat struct{ Name string }
func (d Dog) Speak() string { return d.Name + " says Woof!" } func (c Cat) Speak() string { return c.Name + " says Meow!" }
// Both Dog and Cat satisfy Speaker without declaring it func greet(s Speaker) { fmt.Println(s.Speak()) }
func main() {
greet(Dog{Name: "Rex"}) // "Rex says Woof!"
greet(Cat{Name: "Luna"}) // "Luna says Meow!"
}
`
The Empty Interface
interface{} (or any in Go 1.18+) accepts any value:
`go
func printAnything(v any) {
fmt.Printf("Type: %T, Value: %v\n", v, v)
}
printAnything(42) // Type: int, Value: 42
printAnything("hello") // Type: string, Value: hello
printAnything(true) // Type: bool, Value: true
`
Interface Composition
Build larger interfaces from smaller ones:
`go
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface { Write(p []byte) (n int, err error) }
type ReadWriter interface { Reader Writer }
// Any type implementing both Read and Write satisfies ReadWriter
`
Type Assertions and Type Switches
`go
// Type assertion
func processValue(v any) {
if s, ok := v.(string); ok {
fmt.Println("String:", s)
}
}
// Type switch
func describe(v any) string {
switch val := v.(type) {
case int:
return fmt.Sprintf("Integer: %d", val)
case string:
return fmt.Sprintf("String: %s", val)
case bool:
return fmt.Sprintf("Boolean: %t", val)
default:
return fmt.Sprintf("Unknown: %v", val)
}
}
`
Common Interface Patterns
Stringer (like toString)
`go
type Stringer interface {
String() string
}
type Point struct{ X, Y int }
func (p Point) String() string { return fmt.Sprintf("(%d, %d)", p.X, p.Y) }
fmt.Println(Point{3, 4}) // "(3, 4)"
`
Error Interface
`go
type error interface {
Error() string
}
type NotFoundError struct { Resource string ID string }
func (e *NotFoundError) Error() string {
return fmt.Sprintf("%s with ID %s not found", e.Resource, e.ID)
}
`
Sort Interface
`go
type ByAge []User
func (a ByAge) Len() int { return len(a) } func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age } func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
sort.Sort(ByAge(users)) ``
Best Practices
1. Keep interfaces small — prefer single-method interfaces 2. Define interfaces where they're used, not where they're implemented 3. Accept interfaces, return structs — for maximum flexibility 4. Don't create interfaces prematurely — wait until you need polymorphism 5. Use standard library interfaces (io.Reader, io.Writer, fmt.Stringer) when possible