Skip to content

Interfaces

Scope: what interfaces are, how types satisfy them implicitly, and the two interface patterns you'll use constantly. Builds on Structs and Methods.

The idea in one paragraph

An interface is a list of method signatures — a description of behavior without any implementation. Any type that has those methods automatically satisfies the interface. There's no implements keyword, no registration. If it walks like a duck, Go already considers it a duck.

A concrete example

type Speaker interface {
    Speak() string
}

type Dog struct{ Name string }

func (d Dog) Speak() string {
    return d.Name + " says woof"
}

type Robot struct{ Model string }

func (r Robot) Speak() string {
    return r.Model + " says beep"
}

func Announce(s Speaker) {
    fmt.Println(s.Speak())
}

func main() {
    Announce(Dog{Name: "Rex"})       // Rex says woof
    Announce(Robot{Model: "R2"})     // R2 says beep
}

Neither Dog nor Robot mentions Speaker. They just happen to have a Speak() string method — so both can be passed to Announce. This is implicit satisfaction, and it's the heart of Go's flexibility: you can define an interface after the types, even in a different package, and existing types just work.

Why this matters

  • Decoupling: Announce doesn't care what it's announcing, only that it can Speak(). Test doubles, mocks, and new types slot in without changing existing code.
  • Small interfaces: Go convention favors tiny interfaces — often one method (io.Reader, io.Writer, fmt.Stringer). Big interfaces are brittle; small ones compose.
  • Defined by the consumer: you write the interface where you use the behavior, describing only what you need.

The empty interface: any value

func PrintAnything(v interface{}) {
    fmt.Println(v)
}

interface{} (or its alias any) is satisfied by every type, since it requires zero methods. It's Go's "I accept anything" type — useful for truly generic containers and printing, but it throws away type safety, so don't reach for it by default.

Type switches: asking "what are you?"

func describe(v interface{}) {
    switch t := v.(type) {
    case string:
        fmt.Println("a string:", t)
    case int:
        fmt.Println("an int:", t)
    default:
        fmt.Println("something else")
    }
}

The v.(type) syntax only works in a switch. It's how you recover the concrete type from an interface value when you must.

Interfaces vs other languages

Go Java/C#
Declaring conformance Implicit — just have the methods implements keyword
Who defines it Usually the consumer Usually the producer
Size Tiny (1-2 methods) preferred Often large

Check

  • [ ] You can explain implicit satisfaction in one sentence
  • [ ] You know why Go prefers small interfaces
  • [ ] You can write a type switch over an interface{} value

Next: Error Handling.