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.
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:
...
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.Reader | Read([]byte) (int, error) | file.read() |
io.Writer | Write([]byte) (int, error) | file.write() |
fmt.Stringer | String() string | __str__ |
error | Error() string | __str__ on Exception |
sort.Interface | Len, Less, Swap | __lt__ or key= func |
json.Marshaler | MarshalJSON() ([]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
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."
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.
from typing import TypeVar
T = TypeVar("T")
def first(items: list[T]) -> T:
return items[0]
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)
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.