Go Interfaces Cheat Sheet
Explains Go's implicit interface satisfaction, type assertions and type switches, the empty interface, and idiomatic small-interface design.
Defining & Implementing
Interfaces are satisfied implicitly by matching methods.
type Shape interface { Area() float64 Perimeter() float64}type Rectangle struct { Width, Height float64}func (r Rectangle) Area() float64 { return r.Width * r.Height }func (r Rectangle) Perimeter() float64 { return 2 * (r.Width + r.Height) }// Rectangle implicitly satisfies Shape -- no "implements" keyword neededvar s Shape = Rectangle{Width: 3, Height: 4}fmt.Println(s.Area()) // 12
Type Assertions & Type Switches
Recovering the concrete type behind an interface value.
func describe(s Shape) { if r, ok := s.(Rectangle); ok { // Type assertion with ok-check fmt.Println("It's a rectangle:", r.Width, r.Height) } switch v := s.(type) { // Type switch case Rectangle: fmt.Println("rectangle area:", v.Area()) case Circle: fmt.Println("circle area:", v.Area()) default: fmt.Println("unknown shape") }}
Empty Interface & Stdlib Interfaces
The any type and well-known standard library contracts.
func PrintAny(v any) { // "any" is an alias for interface{} (Go 1.18+) fmt.Println(v)}// Common standard library interfacestype Stringer interface { String() string}type Writer interface { Write(p []byte) (n int, err error)}func (r Rectangle) String() string { return fmt.Sprintf("Rectangle(%vx%v)", r.Width, r.Height) // Satisfies fmt.Stringer}
Concepts
How Go interfaces differ from other languages.
- Implicit satisfaction- A type implements an interface automatically by implementing its methods, no declaration needed
- Interface value- Holds a (type, value) pair internally; a nil interface differs from an interface holding a nil pointer
- Empty interface (any)- interface{} / any can hold a value of any type; it loses compile-time type safety
- Type assertion- v.(T) panics if the type is wrong; v, ok := v.(T) returns ok=false instead of panicking
- Type switch- switch v := x.(type) branches on the dynamic type of an interface value
- Small interfaces- Idiomatic Go favors small, focused interfaces, like io.Reader with just one method
- Interface embedding- Interfaces can embed other interfaces to compose larger contracts
Compile-Time Interface Satisfaction
Force a build error immediately if a type stops satisfying an interface.
type Shape interface { Area() float64}type Rectangle struct{ Width, Height float64 }func (r Rectangle) Area() float64 { return r.Width * r.Height }// Blank identifier assignment: compiled but never executed at runtime.// Fails to build the instant Rectangle no longer implements Shape.var _ Shape = (*Rectangle)(nil)var _ Shape = Rectangle{}// Common in stdlib-style packages to document intent for readers and linters.
The Nil Interface Gotcha
A typed nil pointer stored in an interface is not itself == nil.
type MyError struct{ msg string }func (e *MyError) Error() string { return e.msg }func doWork() *MyError { return nil // no error occurred}func run() error { var err *MyError = doWork() return err // BUG: wraps a non-nil *interface* around a nil *MyError}func main() { if err := run(); err != nil { fmt.Println("got error:", err) // prints, even though doWork() returned nil! } // Fix: return nil explicitly instead of a typed nil pointer, // or check err == (*MyError)(nil) via reflection/errors.As.
Method Values & Method Expressions
Methods can be bound to a receiver or treated as plain functions.
type Counter struct{ n int }func (c *Counter) Inc() { c.n++ }c := &Counter{}increment := c.Inc // method value: receiver c is bound nowincrement()increment()fmt.Println(c.n) // 2// Method expression: receiver becomes the first explicit parameterincExpr := (*Counter).IncincExpr(c)fmt.Println(c.n) // 3// Useful for passing bound behavior into callbacks (sort.Slice, http.HandlerFunc)
Implementing sort.Interface
A classic three-method interface for making any collection sortable.
type ByAge []Personfunc (a ByAge) Len() int { return len(a) }func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }sort.Sort(ByAge(people))// sort.Reverse wraps any sort.Interface and flips Lesssort.Sort(sort.Reverse(ByAge(people)))// Prefer sort.Slice for one-offs -- no named type needed:sort.Slice(people, func(i, j int) bool { return people[i].Age < people[j].Age })
Interface Design Patterns
Idioms used throughout the standard library for composing behavior.
- Interface pollution- Don't define an interface until a second implementation or a test double actually needs it
- io.Reader/io.Writer composition- io.Copy, io.MultiWriter, and io.TeeReader all operate purely on the two smallest possible interfaces
- Interface embedding- io.ReadWriter embeds io.Reader and io.Writer, requiring both method sets be satisfied
- Optional interface checks- if closer, ok := r.(io.Closer); ok { closer.Close() } lets callers probe for extra capability at runtime
- Method set rule- The method set of *T includes both value and pointer receiver methods; T only includes value receiver methods
- Interface satisfaction is structural- Unrelated packages can define types that satisfy the same interface without ever importing each other
- Generics vs interfaces- Use interfaces for behavior (methods); use generic type constraints for operating over multiple concrete types uniformly
Accept interfaces, return concrete types — function parameters should be the narrowest interface needed (like io.Reader), while return values should usually be concrete structs so callers get full functionality.