Designing with Interfaces
Overview
Go interfaces enable polymorphism through implicit satisfaction—types implement interfaces by having matching methods, without explicit declarations.
Interface Basics
type Reader interface {
Read(p []byte) (n int, err error)
}
// Any type with Read method satisfies Reader
type File struct { /* ... */ }
func (f *File) Read(p []byte) (int, error) { /* ... */ }
type Buffer struct { /* ... */ }
func (b *Buffer) Read(p []byte) (int, error) { /* ... */ }Implicit Satisfaction
type Stringer interface {
String() string
}
type Person struct {
Name string
}
// Person implicitly implements Stringer
func (p Person) String() string {
return p.Name
}
var s Stringer = Person{Name: "Alice"} // Works!The Empty Interface
interface{} // Matches any type
any // Alias (Go 1.18+)
func print(v any) {
fmt.Println(v)
}
print(42)
print("hello")
print(true)Interface Values
An interface value holds a (type, value) pair:
var r io.Reader
r = os.Stdin // (type: *os.File, value: stdin)
r = &bytes.Buffer{} // (type: *bytes.Buffer, value: buf)nil Interface vs nil Value
var r io.Reader // nil interface (no type, no value)
var f *os.File // nil pointer
r = f // Interface holds (*os.File, nil)
r == nil // false! Has type, value is nilCommon Patterns
Accept Interface, Return Concrete
// Accept interface
func Process(r io.Reader) error { }
// Return concrete type
func NewBuffer() *bytes.Buffer {
return &bytes.Buffer{}
}Small Interfaces
type Reader interface {
Read([]byte) (int, error)
}
type Writer interface {
Write([]byte) (int, error)
}
type ReadWriter interface {
Reader
Writer
}Assert Interface Compliance
var _ io.Reader = (*MyReader)(nil) // Compile-time checkSummary
| Concept | Description |
|---|---|
| Implicit | No implements keyword |
any/interface{} |
Matches all types |
| Small interfaces | Prefer 1-3 methods |
| Accept interface | Flexible function parameters |
More examples
Example: implicit satisfaction
Save as main.go and go run . (with go mod init example if needed).
package main
import "fmt"
type Speaker interface {
Speak() string
}
type Dog struct{ Name string }
func (d Dog) Speak() string { return d.Name + " says woof" }
func greet(s Speaker) {
fmt.Println(s.Speak())
}
func main() {
// Dog never writes "implements Speaker".
greet(Dog{Name: "Rex"})
}Expected:
Rex says woof
Example: empty interface / any
Save as main.go and go run . (with go mod init example if needed).
package main
import "fmt"
func describe(v any) {
fmt.Printf("%T -> %v\n", v, v)
}
func main() {
describe(42)
describe("hi")
describe(true)
}Expected:
int -> 42
string -> hi
bool -> true
Runnable example
Save as main.go. Then:
go mod init example
go run .package main
import (
"fmt"
"io"
"strings"
)
type Stringer interface {
String() string
}
type Person struct{ Name string }
func (p Person) String() string { return p.Name }
// Implicit satisfaction: no "implements" keyword.
var _ Stringer = Person{}
type ByteCounter int
func (c *ByteCounter) Write(p []byte) (int, error) {
*c += ByteCounter(len(p))
return len(p), nil
}
func dump(r io.Reader) (string, error) {
b, err := io.ReadAll(r)
return string(b), err
}
func main() {
var s Stringer = Person{Name: "Alice"}
fmt.Println("Stringer:", s.String())
// Interface value is a (type, value) pair.
var r io.Reader = strings.NewReader("hello interfaces")
text, err := dump(r)
fmt.Println("from Reader:", text, err)
var c ByteCounter
// *ByteCounter satisfies io.Writer.
_, _ = c.Write([]byte("abcd"))
_, _ = fmt.Fprintf(&c, "ef")
fmt.Println("bytes written:", int(c))
// nil interface vs typed-nil inside interface
var w io.Writer
fmt.Println("nil interface:", w == nil)
var bc *ByteCounter
w = bc
fmt.Println("typed-nil interface equals nil?", w == nil)
}Expected output:
Stringer: Alice
from Reader: hello interfaces <nil>
bytes written: 6
nil interface: true
typed-nil interface equals nil? false
What to notice: Types satisfy interfaces by method set alone. An interface holding a typed nil is not equal to a true nil interface — a common source of surprising error checks.
Try next: Assert compile-time compliance with var _ io.Writer = (*ByteCounter)(nil); pass both strings.Reader and *ByteCounter into one function that only accepts small interfaces.