Section 11

Interfaces

Python has ABCs and Protocols for type contracts. Go has interfaces — and they're implicit. A type satisfies an interface by implementing its methods. No implements keyword, no registration. If it has the methods, it qualifies.

Python
from typing import Protocol

class Writer(Protocol):
    def write(self, data: bytes) -> int: ...

# Must explicitly inherit or use Protocol
class FileWriter:
    def write(self, data: bytes) -> int:
        ...
Go
type Writer interface {
    Write(data []byte) (int, error)
}

// FileWriter satisfies Writer — no declaration needed
type FileWriter struct{ path string }

func (fw *FileWriter) Write(data []byte) (int, error) {
    // ...
}

Standard Library Interfaces

Interface Method Python Equivalent
io.ReaderRead([]byte) (int, error)file.read()
io.WriterWrite([]byte) (int, error)file.write()
fmt.StringerString() string__str__
errorError() string__str__ on Exception
sort.InterfaceLen, Less, Swap__lt__ or key= func
json.MarshalerMarshalJSON() ([]byte, error)__json__ / custom encoder

The Empty Interface

// any (alias for interface{}) accepts any type — like Python's object
func printAnything(v any) {
    fmt.Println(v)
}

// Type assertion — get the concrete type back
s, ok := v.(string)  // ok=false if v isn't a string
Accept Interfaces, Return Structs

This is the most important Go design principle. Functions should accept the narrowest interface they need (e.g., io.Reader instead of *os.File) and return concrete types. This maximizes flexibility for callers while keeping return types explicit. It's the Go equivalent of Python's "program to an interface."

Section 12

Generics

Before Go 1.18 (2022), you either used interface{} with type assertions (unsafe) or wrote the same function for each type (tedious). Generics fix this — type parameters that are checked at compile time.

Python
from typing import TypeVar
T = TypeVar("T")

def first(items: list[T]) -> T:
    return items[0]
Go
func First[T any](items []T) T {
    return items[0]
}

First([]int{1, 2, 3})       // inferred: First[int]
First([]string{"a", "b"})  // inferred: First[string]

Type Constraints

// Constraint: T must support < operator
func Min[T cmp.Ordered](a, b T) T {
    if a < b {
        return a
    }
    return b
}

// Custom constraint
type Number interface {
    ~int | ~int64 | ~float64  // union of underlying types
}

func Sum[T Number](nums []T) T {
    var total T
    for _, n := range nums {
        total += n
    }
    return total
}

Generic Data Structures

type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(v T)  { s.items = append(s.items, v) }
func (s *Stack[T]) Pop() T    { v := s.items[len(s.items)-1]; s.items = s.items[:len(s.items)-1]; return v }

s := &Stack[int]{}
s.Push(42)
Use Generics Sparingly

Go generics are intentionally less powerful than Python's type system or Rust's traits. No higher-kinded types, no associated types, limited method constraints. The community ethos: use generics for general-purpose data structures and functions. For domain-specific code, concrete types and interfaces are clearer.