GGistDev

Interfaces in Go

Interfaces describe behavior (method sets). Implementation is implicit.

Basics

A type implements an interface by having its methods; no explicit implements clause. This enables decoupling and easy testing with fakes.

package main
import "fmt"

type Stringer interface { String() string }

type User struct{ Name string }
func (u User) String() string { return "User(" + u.Name + ")" }

func Print(s Stringer) { fmt.Println(s.String()) }

func main() { Print(User{Name: "Alice"}) }

Type assertion

Extract a concrete value from an interface with a type assertion. The comma-ok form avoids panics on mismatched types.

var any interface{} = 42
if v, ok := any.(int); ok {
    _ = v // 42
}

Type switch

Dispatch on the dynamic type held by an interface value.

func Kind(x interface{}) string {
    switch x.(type) {
    case int:
        return "int"
    case string:
        return "string"
    default:
        return "unknown"
    }
}

Composition

type Reader interface{ Read([]byte) (int, error) }
type Writer interface{ Write([]byte) (int, error) }

type ReadWriter interface {
    Reader
    Writer
}

Nil and zero values

  • A nil interface has no dynamic type or value
  • An interface holding a typed nil pointer is non-nil; compare carefully
var s fmt.Stringer   // nil interface
var p *bytes.Buffer  // nil pointer
var i fmt.Stringer = p // non-nil interface holding typed nil
fmt.Println(i == nil) // false

Best practices

  • Define small, focused interfaces at the consumer side
  • Prefer implicit satisfaction; avoid unnecessary adapter types
  • Return concrete types; accept interfaces (dependency inversion)
  • Document method semantics (ownership, blocking, concurrency)
  • Use interface values sparingly in hot paths to avoid allocations; prefer generics where appropriate

Summary

  • Interfaces enable polymorphism via behavior, not inheritance
  • Use assertions/switches for dynamic type handling when needed