> series: learn go from 0 to 100 | article 6 of 19
Go interfaces are the piece that upgrades the language from “cute little language” to “language Kubernetes and Docker are written in”. And they work backwards from what you expect if you come from Java.
Implicit implementation: no implements
A Go interface is a set of method signatures. Here’s the twist, a type implements it automatically just by having those methods. Nothing gets declared (A Tour of Go, interfaces and implicit implementation):
// An interface defines behavior: signatures only.
type Shape interface {
Area() float64
}
// Rectangle already has Area() from article 5.
// Nothing to declare: it ALREADY implements Shape.
func printArea(s Shape) {
fmt.Printf("area: %.2f\n", s.Area())
}
printArea(Rectangle{Width: 3, Height: 4})
printArea(Circle{Radius: 2})
In Java you write class Rectangle implements Shape and the coupling gets carved into the class. In Go the interface can be defined AFTER the types, even in a package they’ve never heard of. It’s Python’s duck typing (“if it walks like a duck…”) but checked by the compiler: missing method, no compile (official FAQ).
The most profitable interface: io.Writer
The standard library is built on tiny interfaces. The star is io.Writer, a single method:
// io.Writer: the interface that moves the Go world.
// type Writer interface {
// Write(p []byte) (n int, err error)
// }
func greet(w io.Writer) {
fmt.Fprintln(w, "hello from an interface")
}
greet(os.Stdout) // to console
var buf bytes.Buffer
greet(&buf) // to memory (great for tests!)
f, _ := os.Create("log.txt")
greet(f) // to a file
A function that accepts io.Writer writes to console, memory, files, network connections or a gzip stream without changing a line. This is the power that takes dependency injection frameworks elsewhere. Here it’s a parameter.

fmt.Stringer: your type introduces itself
Another one-method interface you’ll use starting today (A Tour of Go, Stringers):
// fmt.Stringer is the stdlib's most famous interface:
// type Stringer interface { String() string }
type Level int
func (n Level) String() string {
switch n {
case 0:
return "DEBUG"
case 1:
return "INFO"
default:
return "?"
}
}
fmt.Println(Level(1)) // INFO, fmt picks it up alone
The fmt package checks at runtime whether your value implements Stringer and uses your String() when printing. It’s Python’s __str__ or Java’s toString(), no inheritance involved. It also fixes article 2‘s little issue: our iota levels now print with names.
Small interfaces: the philosophy
The advice from Effective Go: one and two method interfaces are the norm, not the exception. The house rule: define them where they’re CONSUMED, not next to the implementing type, and ask only for what you need. If your function only writes, ask for an io.Writer, not a whole file.
Compared to the 15-method interfaces seen in other neighborhoods, this is functional minimalism: many small pieces that fit beat one machine that only fits itself. That’s the whole ethos behind Go interfaces: small, implicit and everywhere.
Weekly challenge: put Go interfaces to work
- Bring back Rectangle and Circle from article 5, define a Shape interface with Area() and Perimeter() and loop over a slice of shapes printing both values.
- Implement fmt.Stringer for article 2‘s Level type with all four levels.
- Bonus: write func report(w io.Writer, shapes []Shape) and try it with os.Stdout and a bytes.Buffer. You’ve just written testable code without noticing.
Go interfaces: level complete.
Next article: type assertions, type switches and the empty interface. The corner of interfaces where the surprises hide.
> exit