Interfaces in Go
Interfaces describe behavior (method sets). Implementation is implicit.
Basics
A type implements an interface by having its methods; no explicit implements clause. This enables decoupling and easy testing with fakes.
package main
import "fmt"
type Stringer interface { String() string }
type User struct{ Name string }
func (u User) String() string { return "User(" + u.Name + ")" }
func Print(s Stringer) { fmt.Println(s.String()) }
func main() { Print(User{Name: "Alice"}) }
Type assertion
Extract a concrete value from an interface with a type assertion. The comma-ok form avoids panics on mismatched types.
var any interface{} = 42
if v, ok := any.(int); ok {
_ = v // 42
}
Type switch
Dispatch on the dynamic type held by an interface value.
func Kind(x interface{}) string {
switch x.(type) {
case int:
return "int"
case string:
return "string"
default:
return "unknown"
}
}
Composition
type Reader interface{ Read([]byte) (int, error) }
type Writer interface{ Write([]byte) (int, error) }
type ReadWriter interface {
Reader
Writer
}
Nil and zero values
- A nil interface has no dynamic type or value
- An interface holding a typed nil pointer is non-nil; compare carefully
var s fmt.Stringer // nil interface
var p *bytes.Buffer // nil pointer
var i fmt.Stringer = p // non-nil interface holding typed nil
fmt.Println(i == nil) // false
Best practices
- Define small, focused interfaces at the consumer side
- Prefer implicit satisfaction; avoid unnecessary adapter types
- Return concrete types; accept interfaces (dependency inversion)
- Document method semantics (ownership, blocking, concurrency)
- Use interface values sparingly in hot paths to avoid allocations; prefer generics where appropriate
Summary
- Interfaces enable polymorphism via behavior, not inheritance
- Use assertions/switches for dynamic type handling when needed