Interfaces | Go - Wyatt's Notes
Interface Basics
Section titled “Interface Basics”An interface in Go defines a set of method signatures. A type satisfies an interface by implementing All of its methods. There is no explicit implements declaration — satisfaction is implicit and Structural.
type Speaker interface { Speak() string}
type Dog struct { Name string}
func (d Dog) Speak() string { return "Woof"}
func main() { var s Speaker = Dog{Name: "Rex"} fmt.Println(s.Speak()) // Woof}Any type that has a Speak() string method satisfies SpeakerRegardless of its position in the Type hierarchy.
Interface Composition
Section titled “Interface Composition”Interfaces can be composed from other interfaces:
type Reader interface { Read(p []byte) (n int, err error)}
type Writer interface { Write(p []byte) (n int, err error)}
type Closer interface { Close() error}
type ReadWriteCloser interface { Reader Writer Closer}ReadWriteCloser requires all methods from Reader``WriterAnd Closer.
The Empty Interface
Section titled “The Empty Interface”interface{} (or any since Go 1.18) is the empty interface, satisfied by every type:
var x anyx = 42x = "hello"x = []int{1, 2, 3}The empty interface is a escape hatch when you need to store values of different types. It should be Used sparingly — it bypasses the type system and requires runtime type assertions to use safely.
When to Use any
Section titled “When to Use any”Acceptable uses:
- Format functions (
fmt.Printf): the caller does not know what types they are passing - Generic containers before Go 1.18 generics
- Interoperability with untyped external data (JSON, protocol buffers)
- Heterogeneous collections that are genuinely heterogeneous (e.g., configuration values)
Avoid when:
- You know the concrete type at compile time
- A parameter accepts a small, known set of types (use a union type or an interface)
- You are passing
anythrough multiple function layers without ever checking the type
Type Assertions
Section titled “Type Assertions”A type assertion extracts the concrete value from an interface:
var i any = "hello"
s := i.(string) // panic if i does not hold a stringfmt.Println(s) // "hello"
s, ok := i.(string) // safe: ok is false if assertion failsif ok { fmt.Println(s)}Always use the comma-ok idiom unless you are certain of the type:
func process(v any) { if s, ok := v.(string); ok { fmt.Println("string:", s) } else if n, ok := v.(int); ok { fmt.Println("int:", n) }}Type Switches
Section titled “Type Switches”A type switch performs multiple type assertions in sequence:
func inspect(v any) { switch val := v.(type) { case int: fmt.Printf("int: %d\n", val) case string: fmt.Printf("string: %s (len %d)\n", val, len(val)) case bool: fmt.Printf("bool: %t\n", val) case nil: fmt.Println("nil") default: fmt.Printf("unknown: %T\n", val) }}In a type switch, val has the type of the matched case. In the default case, val has the same Type as v (the interface type).
Type Switch with Interface Cases
Section titled “Type Switch with Interface Cases”Cases can also be interfaces:
switch v.(type) {case io.Reader: fmt.Println("implements io.Reader")case io.Writer: fmt.Println("implements io.Writer")case io.ReadWriter: fmt.Println("implements io.ReadWriter")}A value matches the first case whose interface it satisfies.
Pointer vs Value Receivers and Interfaces
Section titled “Pointer vs Value Receivers and Interfaces”This is a critical subtlety. The method set of T includes only value receiver methods. The method Set of *T includes all methods. This affects interface satisfaction:
type Foo interface { Bar()}
type MyType struct{}
func (m *MyType) Bar() {} // pointer receiver
func main() { var f Foo = MyType{} // compile error: MyType does not implement Foo var f Foo = &MyType{} // OK: *MyType implements Foo}The rule: if any method of an interface has a pointer receiver, only a pointer to the type (not the Value itself) satisfies the interface. This is because calling a pointer receiver on a copy of the Value would be meaningless — the method modifies the receiver, but the modification is lost when The copy is discarded.
Nil Interface Values
Section titled “Nil Interface Values”An interface value is a two-word tuple: (type, value). A nil interface has both type and value set To nil:
var s Speakerfmt.Println(s == nil) // trueA non-nil interface can hold a nil value:
var p *Dogvar s Speaker = p // type is *Dog, value is nilfmt.Println(s == nil) // false!This is a common source of bugs. The interface is not nil because its type is *DogEven though The value is nil. Calling a method on it works (the receiver is nil inside the method), but Comparing the interface to nil does not detect it.
func process(s Speaker) { if s == nil { fmt.Println("nil interface") return } fmt.Println(s.Speak()) // may panic if the concrete value is nil}Interface Zero Value
Section titled “Interface Zero Value”The zero value of an interface is nil. Calling any method on a nil interface causes a panic:
var s Speakers.Speak() // panic: runtime error: invalid memory address or nil pointer dereferenceAlways check for nil before calling methods on an interface, or ensure the interface is always Initialized.
Common Interfaces
Section titled “Common Interfaces”Go”s standard library defines several pervasive interfaces:
| Interface | Methods | Used By |
|---|---|---|
io.Reader | Read(p []byte) (n int, err error) | Files, network connections |
io.Writer | Write(p []byte) (n int, err error) | Files, buffers, HTTP responses |
io.Closer | Close() error | Files, connections |
fmt.Stringer | String() string | Custom string representation |
error | Error() string | Error values |
sort.Interface | Len() int``Less(i,j int) bool``Swap(i,j int) | Sorting |
Implementing fmt.Stringer:
type IPAddr [4]byte
func (ip IPAddr) String() string { return fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])}
fmt.Println(IPAddr{127, 0, 0, 1}) // 127.0.0.1Intuition
Section titled “Intuition”Interfaces are behavioral contracts, not inheritance hierarchies: In Go, you don’t declare that a type implements an interface — it just does. If a duck walks like a duck and quacks like a duck, it satisfies the Duck interface. This structural typing means interfaces emerge logically from concrete types, not from forced inheritance trees.
Why it matters: Interfaces enable dependency injection, testability, and polymorphism without complex class hierarchies. The standard library is built on small interfaces (io.Reader, io.Writer) that thousands of types satisfy. Understanding interface satisfaction, pointer receivers, and nil interfaces is essential for writing idiomatic Go.
The key insight: An interface value is a (type, value) tuple — even if the value is nil, the interface itself is not nil if it has a type. This is the most common source of nil interface bugs.
Common Pitfalls
Section titled “Common Pitfalls”Nil interface with non-nil concrete type. An interface holding a
*TwhereTis nil is not equal tonil. Use reflection (reflect.ValueOf(i).IsNil()) or check the concrete value if this situation is possible.Pointer receiver methods and interface satisfaction. If an interface method has a pointer receiver, only
*Tsatisfies the interface, notT. This is the single most common interface compilation error.Overusing
any. Passinganythrough function signatures defeats the purpose of the type system. Prefer specific interfaces. Use generics (Go 1.18+) when you need to abstract over types while maintaining type safety.Duck typing confusion. Go interfaces are structural, not nominal. Two independently defined types with the same method set satisfy the same interface. This is a feature, but it means you can accidentally satisfy an interface you did not intend to.
Empty interface in slices.
[]anyis a common pattern but loses type safety. Since Go 1.18, prefer generics:[]TwhereTis a type parameter.
flowchart TD
A[Interfaces] --> B[Key Concepts]
A --> C[Core Principles]
A --> D[Practical Applications]
B --> E[Fundamental definitions]
C --> F[Design patterns]
D --> G[Real-world usage]Summary
Section titled “Summary”This topic covers the core concepts of interfaces, including underlying theory, practical implementation, and key applications.
Key concepts include:
- core concepts and terminology
- algorithms and computational thinking
- practical implementation
- security and ethical considerations
- applications in the real world
Understanding these concepts thoroughly is essential for both examinations and practical programming, and requires both theoretical knowledge and hands-on practice.
Worked Examples
Section titled “Worked Examples”Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.
Cross-References
Section titled “Cross-References”- Generics: Generic programming as an alternative to interface-based abstraction.
- Error Handling: Error handling patterns using the error interface.
- Practice Interfaces: Practice problems covering interface design and implementation.