Interfaces
What is an interface?
Section titled “What is an interface?”An interface is a named collection of method signatures. Any type that has all of those methods satisfies the interface — without saying so explicitly.
type Shape interface { Area() float64 Perimeter() float64}Any type with an Area() float64 method and a Perimeter() float64 method automatically satisfies Shape.
Implicit satisfaction — no implements
Section titled “Implicit satisfaction — no implements”This is the defining feature of Go interfaces. Compare:
| Language | How you declare satisfaction |
|---|---|
| Java / C# | class Circle implements Shape |
| Go | Just have the right methods |
The result is decoupled code. A type in package geometry can satisfy an interface in package renderer without either package knowing about the other. This makes Go interfaces composable across package boundaries without any import dependency on the interface definition.
Small interfaces
Section titled “Small interfaces”Go’s standard library is built on tiny, focused interfaces:
type Reader interface { Read(p []byte) (n int, err error) }type Writer interface { Write(p []byte) (n int, err error) }type Stringer interface { String() string }The Go proverb: “The bigger the interface, the weaker the abstraction.” A two-method interface is satisfied by far more types than a ten-method interface, making it dramatically more reusable.
The empty interface — any
Section titled “The empty interface — any”An interface with no methods is satisfied by every type. Since Go 1.18 it has the alias any:
var v any = 42v = "hello"v = []int{1, 2, 3}Use any sparingly — it surrenders type safety. The typical use cases are generic containers (before generics were added), JSON unmarshalling into unknown shapes, and variadic logging functions.
Accept interfaces, return structs
Section titled “Accept interfaces, return structs”The idiomatic Go guideline:
- Function parameters should be interfaces — accept the broadest type that covers what you actually call.
- Return values should be concrete structs — callers get the full API of the real type; they can narrow it themselves.
// Good: accepts an interface, returns a concrete typefunc NewBuffer(r io.Reader) *bytes.Buffer { ... }package main
import ( "fmt" "math")
type Shape interface { Area() float64 Perimeter() float64}
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}
type Rect struct { W, H float64}
func (r Rect) Area() float64 { return r.W * r.H }func (r Rect) Perimeter() float64 { return 2 * (r.W + r.H) }
func describe(s Shape) { fmt.Printf("area=%.2f perimeter=%.2f\n", s.Area(), s.Perimeter())}
func main() { shapes := []Shape{ Circle{Radius: 5}, Rect{W: 4, H: 3}, } for _, s := range shapes { describe(s) }}Loading Go runtime (first run only, ~8 MB)…