Interfaces

Implicit satisfaction, the empty interface, type assertions, and interface composition.

Intermediate 40 min read 🐹 Go

Implicit Interface Satisfaction

Go interfaces are implicitly satisfied. There's no implements keyword. If a type has all the methods an interface requires, it satisfies that interface automatically. This is Go's most powerful design decision — it enables decoupling without forcing type hierarchies.

// Define an interface
type Shape interface {
    Area() float64
    Perimeter() float64
}

// Circle satisfies Shape (no "implements" needed)
type Circle struct{ Radius float64 }
func (c Circle) Area() float64      { return math.Pi * c.Radius * c.Radius }
func (c Circle) Perimeter() float64 { return 2 * math.Pi * c.Radius }

// Rectangle also satisfies Shape
type Rect struct{ Width, Height float64 }
func (r Rect) Area() float64      { return r.Width * r.Height }
func (r Rect) Perimeter() float64 { return 2 * (r.Width + r.Height) }

// Function accepts any Shape
func printShapeInfo(s Shape) {
    fmt.Printf("Area: %.2f, Perimeter: %.2f\n", s.Area(), s.Perimeter())
}

printShapeInfo(Circle{Radius: 5})
printShapeInfo(Rect{Width: 10, Height: 5})
Output
Area: 78.54, Perimeter: 31.42
Area: 50.00, Perimeter: 30.00
Key Takeaway: Define interfaces where they're used, not where they're implemented. Keep interfaces small — the standard library's most powerful interfaces have 1-2 methods (io.Reader, io.Writer, error, fmt.Stringer).

Key Standard Library Interfaces

InterfaceMethodsUsed By
io.ReaderRead(p []byte) (n int, err error)Files, HTTP bodies, buffers, compression
io.WriterWrite(p []byte) (n int, err error)Files, HTTP responses, buffers
fmt.StringerString() stringCustom print formatting
errorError() stringAll error handling
sort.InterfaceLen(), Less(i,j), Swap(i,j)Custom sorting

Empty Interface and Type Assertions

// any (alias for interface{}) accepts ANY type
func printType(v any) {
    // Type switch
    switch val := v.(type) {
    case int:
        fmt.Printf("Integer: %d\n", val)
    case string:
        fmt.Printf("String: %q\n", val)
    default:
        fmt.Printf("Unknown: %v\n", val)
    }
}

printType(42)
printType("hello")
printType(3.14)

// Type assertion (single type check)
var i any = "hello"
s, ok := i.(string) // comma-ok pattern
if ok {
    fmt.Println("String value:", s)
}
Output
Integer: 42
String: "hello"
Unknown: 3.14
String value: hello

⚠️ Common Mistake: Overusing interface{}/any

Using any everywhere defeats the purpose of Go's type system. Use specific interfaces or generics instead. any should be a last resort, not a default.

Interface Satisfaction (Implicit!)
io.Reader
Read([]byte) (int, error)
io.Writer
Write([]byte) (int, error)
fmt.Stringer
String() string
error
Error() string
sort.Interface
Len, Less, Swap
http.Handler
ServeHTTP(w, r)

Practice Exercises

Medium Build a Mini Project

Combine concepts from this tutorial to build a small utility or tool.

Medium Debug Challenge

Introduce a bug in one of the code examples and practice finding and fixing it.

Hard Refactoring Exercise

Rewrite one example using a different approach and compare the tradeoffs.