Interfaces

Go interfaces are satisfied implicitly. If a type has the methods an interface requires, it implements that interface - no implements keyword needed. This decouples packages and enables powerful composition.

Implicit Satisfaction

type Speaker interface {
    Speak() string
}

type Dog struct{ Name string }

func (d Dog) Speak() string {
    return d.Name + " says woof!"
}

// Dog implements Speaker without declaring it.
// This compiles because Dog has a Speak() string method.
var s Speaker = Dog{Name: "Rex"}
fmt.Println(s.Speak()) // "Rex says woof!"

The compiler verifies satisfaction at the assignment point. If Dog is missing the Speak method, you get a compile error.

Small Interfaces

Go’s best interfaces are small, often just one method. The stdlib is full of them:

InterfaceMethodWhat it means
io.ReaderRead(p []byte) (n int, err error)Can read bytes
io.WriterWrite(p []byte) (n int, err error)Can write bytes
fmt.StringerString() stringHas string representation
errorError() stringIs an error
sort.InterfaceLen, Less, SwapCan be sorted
http.HandlerServeHTTP(w, r)Handles HTTP requests
// Anything with a Read method works with io.Copy, json.Decoder, etc.
type MyReader struct{ data []byte; pos int }

func (r *MyReader) Read(p []byte) (int, error) {
    if r.pos >= len(r.data) {
        return 0, io.EOF
    }
    n := copy(p, r.data[r.pos:])
    r.pos += n
    return n, nil
}

Tip: “The bigger the interface, the weaker the abstraction.” - Rob Pike. Keep interfaces to 1-3 methods.

Embedding for Composition

Combine small interfaces into larger ones:

type ReadWriter interface {
    io.Reader
    io.Writer
}

type ReadWriteCloser interface {
    io.Reader
    io.Writer
    io.Closer
}

Struct embedding works similarly:

type Logger struct {
    io.Writer // embeds Writer - Logger gains Write method
    prefix string
}

func (l Logger) Log(msg string) {
    fmt.Fprintf(l, "[%s] %s\n", l.prefix, msg)
}

logger := Logger{Writer: os.Stdout, prefix: "APP"}
logger.Log("starting up")

Accept Interfaces, Return Structs

This is Go’s most important interface guideline:

// Good: accepts interface - works with any Reader
func CountLines(r io.Reader) (int, error) {
    scanner := bufio.NewScanner(r)
    count := 0
    for scanner.Scan() {
        count++
    }
    return count, scanner.Err()
}

// Works with files, HTTP responses, strings, buffers...
CountLines(os.Stdin)
CountLines(resp.Body)
CountLines(strings.NewReader("line1\nline2"))

Why return structs? Concrete types are easier to construct, document, and extend without breaking changes. Define interfaces at the call site, not the implementation.

// Define the interface where you USE it, not where you implement it
package myservice

type Store interface {
    Get(ctx context.Context, id string) (Item, error)
    Put(ctx context.Context, item Item) error
}

type Service struct {
    store Store
}

func New(store Store) *Service {
    return &Service{store: store}
}

The any Type and Type Assertions

any is an alias for interface{} (the empty interface). Any value satisfies it.

func printType(v any) {
    switch val := v.(type) {
    case string:
        fmt.Println("string:", val)
    case int:
        fmt.Println("int:", val)
    case []byte:
        fmt.Println("bytes:", len(val))
    default:
        fmt.Printf("unknown: %T\n", val)
    }
}

Type assertions extract the concrete value:

var i any = "hello"

s := i.(string)     // panics if i is not a string
s, ok := i.(string) // safe - ok is false if wrong type
if ok {
    fmt.Println(s)
}

Gotcha: Prefer generics over any for type-safe containers. Use any for truly heterogeneous data (JSON parsing, plugin systems).

Interface Compliance Check

Verify at compile time that a type satisfies an interface:

// Compile-time check: *Dog must implement Speaker
var _ Speaker = (*Dog)(nil)

This costs nothing at runtime and catches missing methods early.

Nil Interface Pitfall

An interface value is nil only when both its type and value are nil:

var p *Dog = nil
var s Speaker = p

fmt.Println(s == nil) // false! s has type *Dog, value nil
fmt.Println(p == nil) // true

This is the single most common interface gotcha in Go. See Gotchas for details.

Next: Error Handling | Concurrency