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:
Announcedoesn't care what it's announcing, only that it canSpeak(). 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.