Learn Go
intermediate1 min read

Interfaces

An interface in Go is a set of method signatures. A type satisfies an interface simply by implementing all those methods — there is no implements keyword. This implicit satisfaction makes Go interfaces lightweight and highly composable.

Defining and Implementing an Interface

package main
 
import (
    "fmt"
    "math"
)
 
type Shape interface {
    Area() float64
    Perimeter() float64
}
 
type Circle struct{ Radius float64 }
type Rectangle struct{ Width, Height float64 }
 
func (c Circle) Area() float64      { return math.Pi * c.Radius * c.Radius }
func (c Circle) Perimeter() float64 { return 2 * math.Pi * c.Radius }
 
func (r Rectangle) Area() float64      { return r.Width * r.Height }
func (r Rectangle) Perimeter() float64 { return 2 * (r.Width + r.Height) }
 
func printShape(s Shape) {
    fmt.Printf("Area: %.2f  Perimeter: %.2f\n", s.Area(), s.Perimeter())
}
 
func main() {
    printShape(Circle{Radius: 5})
    printShape(Rectangle{Width: 3, Height: 4})
}

The io.Writer and io.Reader Pattern

The standard library's most important interfaces are io.Writer and io.Reader. Anything that implements Write([]byte) (int, error) is an io.Writer:

package main
 
import (
    "bytes"
    "fmt"
    "io"
    "strings"
)
 
func writeAll(w io.Writer, messages []string) error {
    for _, m := range messages {
        if _, err := fmt.Fprintln(w, m); err != nil {
            return err
        }
    }
    return nil
}
 
func main() {
    var buf bytes.Buffer
    writeAll(&buf, []string{"Hello", "from", "Go"})
    fmt.Print(buf.String())
 
    writeAll(strings.NewReader(""), []string{}) // also valid — Reader is Writer? No.
    // But os.Stdout implements io.Writer, so:
    writeAll(writerOnly{}, []string{"direct to stdout would work too"})
}
 
type writerOnly struct{}
 
func (writerOnly) Write(p []byte) (int, error) { return len(p), nil }

Idiomatic Go: Accept interfaces, return concrete types. Design functions to accept the smallest interface that satisfies your needs.

The Empty Interface

Before Go 1.18 generics, interface{} (now also written any) was used to represent a value of any type:

package main
 
import "fmt"
 
func describe(v any) {
    fmt.Printf("value=%v  type=%T\n", v, v)
}
 
func main() {
    describe(42)
    describe("hello")
    describe([]int{1, 2, 3})
    describe(nil)
}

Use any sparingly — it sacrifices type safety. Prefer generics (Go 1.18+) for new code.

Type Assertions

A type assertion extracts the concrete value from an interface:

package main
 
import "fmt"
 
func main() {
    var i any = "hello"
 
    // Single-value form — panics if wrong type
    s := i.(string)
    fmt.Println(s, len(s))
 
    // Two-value form — safe
    if n, ok := i.(int); ok {
        fmt.Println("int:", n)
    } else {
        fmt.Println("not an int") // this branch runs
    }
}

Type Switches

A type switch branches on the dynamic type of an interface value:

package main
 
import "fmt"
 
func classify(v any) string {
    switch x := v.(type) {
    case int:
        return fmt.Sprintf("int: %d", x)
    case string:
        return fmt.Sprintf("string: %q (len %d)", x, len(x))
    case bool:
        return fmt.Sprintf("bool: %t", x)
    case []int:
        return fmt.Sprintf("[]int with %d elements", len(x))
    default:
        return fmt.Sprintf("unknown type: %T", x)
    }
}
 
func main() {
    fmt.Println(classify(42))
    fmt.Println(classify("gopher"))
    fmt.Println(classify(true))
    fmt.Println(classify([]int{1, 2, 3}))
}

Interface Composition

Interfaces can embed other interfaces to build richer contracts:

package main
 
import (
    "fmt"
    "io"
)
 
type ReadWriter interface {
    io.Reader
    io.Writer
}
 
func copy(dst io.Writer, src io.Reader) (int64, error) {
    return io.Copy(dst, src)
}
 
func main() {
    // bytes.Buffer satisfies both io.Reader and io.Writer
    var buf io.ReadWriter = &struct {
        read  func([]byte) (int, error)
        write func([]byte) (int, error)
    }{} // simplified; bytes.Buffer is the real-world example
    fmt.Printf("%T\n", buf)
}

Key Takeaways