What Go optimises for

Go was designed at Google for large teams, code that lives a long time, and fast builds. Most of its decisions follow from that.

Principle In practice
Simplicity Few keywords and features. If there were two ways to do something, one was removed.
Readability first Code is read far more often than it is written. Clear beats clever; “boring” is a compliment.
Composition No class hierarchies. You combine small types and small interfaces.
Explicit over magic No annotations and no hidden control flow. Errors are ordinary return values.
Concurrency built in Goroutines and channels are part of the language, not a library.
Fast tooling Builds take seconds, the output is one static binary, and there is one official formatter.
Useful zero values nil, "", 0, false; well-designed types work without initialisation.
Batteries included A rich standard library (HTTP, JSON, crypto, testing, profiling) and small third-party libraries instead of frameworks.

Object orientation without classes

Go has types, methods and interfaces, but no classes and no inheritance. The usual concepts still have a place:

Concept How Go does it
Class Named type (usually a struct) + methods
Encapsulation Lowercase identifiers; the package is the boundary
Abstraction Interface (small, declared by the consumer)
Reuse Struct embedding (composition + method promotion)
Overriding A method on the outer type shadows the embedded one
Calling the “parent” Explicit call through the embedded field: r.BaseRepo.Save(o)
Dynamic dispatch Interface values (the only kind there is)
Combining contracts Interface embedding: interface { Reader; Writer }
Parametric polymorphism Type parameters (Go 1.18+)
Overloading Not supported: different names, variadics or options
Constructor NewX() function, composite literal, or the zero value
Static members Package-level variables and functions
Runtime type checks Type assertion x.(T), type switch
Enums Typed constants with iota
String form / equality String() (fmt.Stringer), == on comparable structs
Callbacks Closures and function types

Details in Types, Interfaces & Composition.


Common design problems, Go idioms

Most classic design patterns shrink to a function or an interface.

Problem Go idiom
Shared algorithm, varying steps A function that takes an interface
Swappable behaviour (strategy) A function type or a one-method interface
Wrapping behaviour (decorator, middleware) A type that holds an interface and implements it again; func(http.Handler) http.Handler
Many optional parameters Functional options, or a Config/Deps struct
One shared instance Package-level var + sync.Once / sync.OnceValue
Event listener Closure
Constructing variants NewX() returning an interface or struct
Dependency injection Pass dependencies into NewX() explicitly in main

Dependency injection is just wiring in main:

store := postgres.Open(ctx, dsn)
svc   := service.New(store, clock, logger)
api   := httpapi.New(httpapi.Deps{Service: svc, Log: logger})

No container, no reflection. The compiler checks the whole graph.


Think in Go

  1. Start concrete. Write structs and functions first. Introduce an interface when a second consumer actually needs one.
  2. Handle errors where they occur. Add context with %w. Don’t reach for panic.
  3. Prefer boring code. If it needs a comment to be understood, make it simpler instead.
  4. Let gofmt decide formatting. No style debates.
  5. Measure before you optimise. The profiler regularly disagrees with the obvious fix (see Testing & Profiling).

Where to go next