Go Interfaces

Go interfaces are satisfied implicitly — a type implements an interface just by having the right methods. No implements keyword needed. This makes Go interfaces uniquely flexible and composable.

Defining an Interface

Go
type Writer interface {
    Write(p []byte) (n int, err error)
}

This says: "anything that has a Write method with this signature is a Writer."

Implicit Satisfaction

Go
type Logger struct {
    prefix string
}

func (l Logger) Write(p []byte) (n int, err error) {
    fmt.Printf("[%s] %s\n", l.prefix, string(p))
    return len(p), nil
}

// Logger implements Writer WITHOUT declaring it
var w Writer = Logger{prefix: "APP"}

This is the key insight: types satisfy interfaces just by having the right methods. No inheritance, no declaration, no keywords.

Interface Composition

Combine small interfaces into larger ones:

Go
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

type ReadWriter interface {
    Reader
    Writer
}

Go's standard library uses this pattern extensively — io.ReadWriter, io.ReadCloser, etc.

Empty Interface

interface{} (or any in Go 1.18+) accepts any type:

Go
func printAny(v any) {
    fmt.Printf("%v (%T)\n", v, v)
}

printAny(42)        // 42 (int)
printAny("hello")   // hello (string)
printAny(true)      // true (bool)

Use sparingly — it defeats the purpose of static typing.

Why Interfaces Matter

Interfaces let you write functions that accept behaviour rather than concrete types:

Go
func process(w Writer) error {
    _, err := w.Write([]byte("hello"))
    return err
}

This function works with Logger, File, Buffer, or anything else that implements Write.

The io.Reader Pattern

Go's most famous interface — just one method:

Go
type Reader interface {
    Read(p []byte) (n int, err error)
}

Files, HTTP responses, buffers, gzip readers — anything that produces bytes implements io.Reader. This is how Go achieves its remarkable composability.

Tips

  • Keep interfaces small — 1-3 methods is ideal.
  • Accept interfaces, return structs.
  • Use any sparingly — prefer specific types.
  • Go interfaces are about behaviour, not hierarchy.

Next: concurrency — goroutines and channels.