Designing with Interfaces

Updated

July 30, 2026

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 nil

Common 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 check

Summary

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.