Go — Reference

Source: https://go.dev/doc/

Go

  • Created: 2009 by Robert Griesemer, Rob Pike, Ken Thompson at Google
  • Latest stable: Go 1.26.2 (April 2026); Go 1.26.0 released 2026-02-10
  • Paradigms: imperative, concurrent, structurally-typed, lightly object-oriented (no inheritance), generic (since 1.18)
  • Typing: static, strong, structural for interfaces, type inference at declaration
  • Memory: GC (concurrent low-latency tri-color mark-sweep, sub-ms STW)
  • Compilation: AOT to single static native binary; very fast compile times; no runtime dependency on libc by default
  • Primary domains: backend services, microservices, CLI tools, infrastructure (Docker, Kubernetes, Terraform), networking, observability, build tooling
  • Official docs: https://go.dev/doc/

1. At a glance

  • Stewarded by the Go team at Google, governed by the open-source proposal process on GitHub.
  • 6-month release cadence (February + August). Go 1.x is stable: the Go 1 compatibility promise means working code keeps compiling.
  • One implementation: gc (the official compiler). gccgo exists but is not actively developed at the same pace. TinyGo for embedded/WASM.
  • Cross-compilation is built in: GOOS=linux GOARCH=arm64 go build.

2. Getting started

  • Install: Official tarball/installer at https://go.dev/dl/. Linux: extract to /usr/local/go, add to PATH. macOS: brew install go. Windows: winget install GoLang.Go or MSI.

  • Version manager: none official. g (https://github.com/stefanmaric/g), gvm, asdf. Or go install golang.org/dl/go1.26@latest && go1.26 download.

  • Hello, world:

    package main
    import "fmt"
    func main() { fmt.Println("Hello, world!") }

    Run: go run . or go build -o hello && ./hello.

  • Project layout: go.mod at module root; cmd/<name>/main.go for binaries, internal/ for non-exported packages, pkg/ (rare) for reusable libs.

  • Build tool: go CLI is everything: go build, go test, go run, go install, go mod, go work, go vet, go fmt, go generate, go tool pprof, go tool trace.

  • REPL/playground: The Go Playground (https://go.dev/play/) — sandboxed, sharable. gore (community REPL).

3. Basics

  • Primitives: bool, string, int/int8/int16/int32/int64 (int is platform width — 64-bit on modern systems), uint*, uintptr, byte (=uint8), rune (=int32 Unicode codepoint), float32/float64, complex64/complex128. No char type; iterate strings with for i, r := range s to get runes. Literals: 0x, 0b, 0o, _ separators (Go 1.13+), backtick raw strings (no escapes; can include newlines).
  • Variables: var x int = 1, short declaration x := 1 (function scope only, := requires at least one new var on LHS), const (compile-time, untyped by default), iota for enum-like sequences (const (A = iota; B; C) gives 0, 1, 2). Block scope. No unused imports/variables (compile error — use _ to discard, or _ = x to keep). Zero-value default: numeric 0, bool false, string "", slice/map/chan/func/pointer nil.
  • Control flow: if/else (with init: if v, err := f(); err == nil { ... }v and err are if-scoped), switch (no fallthrough by default; switch x.(type) for type switch on interface values; tag-less switch { case cond: } replaces long if/else-if chains), for (only loop keyword: for i := 0; i < n; i++, for cond, for { } infinite, for k, v := range coll, for i := range 10 since 1.22 ranges int), goto (rare; permitted), defer fn() (LIFO, runs on function return). range over functions (Go 1.23+) for custom iterators via iter.Seq[V] / iter.Seq2[K,V].
  • Functions: multiple return values (func f() (int, error)), named returns (func f() (v int, err error) { ... return } returns named vars), variadic func f(xs ...int) (call with f(1,2,3) or f(slice...)), closures (capture-by-reference), first-class. Methods are functions with a receiver: func (r Rect) Area() float64 (value receiver — copies) vs func (r *Rect) Scale(f float64) (pointer receiver — mutates). Interface methods can be value- or pointer-receiver.
  • Strings: immutable UTF-8 byte sequences (stored as 2-word header: pointer + length). Indexing s[i] returns a byte; for i, r := range s iterates runes (i is byte offset). No interpolation; use fmt.Sprintf("%v %v", a, b) or fmt.Sprintf("%d-%s", n, name). strings.Builder for efficient O(n) concat (avoids quadratic copying). Convert: string([]byte{...}), []byte(s), []rune(s). UTF-8 utilities: unicode/utf8, unicode.
  • Collections: arrays [N]T (fixed size, value type — copied on assignment), slices []T (the workhorse — pointer + len + cap header, lightweight reference into a backing array), maps map[K]V (hash table, randomized iteration order), channels chan T (typed FIFO with optional buffer). No tuples (use multiple returns or structs). No sets (idiomatic: map[T]struct{}{} — zero-byte value). No generics on methods (top-level funcs + type defs only as of 1.26). len(), cap(), make(), new(), append(), copy(), delete() are builtins.

4. Intermediate

  • Generics (1.18+): func Map[T, U any](s []T, f func(T) U) []U. Type sets via interfaces with ~int | ~float64 (the ~ allows defined types like type MyInt int). any is alias for interface{} (1.18+). comparable built-in constraint covers types usable as map keys. golang.org/x/exp/constraints provides Ordered, Integer, Float, etc. Generic type aliases (1.24+): type Set[T comparable] = map[T]struct{}.

  • Modules/packages: go mod is the dependency system. Module = repo + go.mod (declares module path + Go version + dependencies); package = directory of .go files with same package decl. Workspaces (go.work, 1.18+) for multi-module local development. Module graph: go mod graph. Minimum-version selection (MVS) — go picks the minimum version satisfying constraints, not the maximum like SemVer-resolvers in npm/Cargo. go.sum records cryptographic hashes; verified against GOSUMDB (sum.golang.org by default). GOPRIVATE/GONOPROXY/GONOSUMDB to bypass for private modules.

  • Error handling: errors are values (type error interface { Error() string }). Convention: func f() (T, error); check if err != nil. errors.Is(err, sentinel) / errors.As(err, &target) for wrapping (Go 1.13+); fmt.Errorf("loading config: %w", err) to wrap with attached context. panic / recover for unrecoverable / library-boundary recovery; not for control flow. errors.Join (1.20+) combines multiple errors into one.

    func work() error {
        cfg, err := loadConfig()
        if err != nil { return fmt.Errorf("loading config: %w", err) }
        return process(cfg)
    }
    // Caller:
    if err := work(); err != nil {
        var nf *NotFoundError
        if errors.As(err, &nf) { /* typed handling */ }
        log.Printf("work failed: %v", err)
    }
  • Concurrency: goroutines (go f() — multiplexed onto OS threads by GMP scheduler; 8 KiB initial stack, grows on demand), channels (ch := make(chan T) unbuffered, make(chan T, 16) buffered, <-ch send/receive, close(ch) signal end; receiving from a closed channel returns zero value + ok=false), select for multiplexing (case <-ch: / case ch <- x: / case <-ctx.Done(): / default:), sync.WaitGroup (counter for fan-out/in), sync.Mutex/RWMutex, sync.Once (lazy init), sync.Map (specialized — usually a plain map + mutex is better), sync/atomic (typed atomic.Int64/Pointer[T] since 1.19), context.Context for deadlines / cancellation / request-scoped values. Higher-level: golang.org/x/sync/errgroup for fan-out with first-error cancellation, golang.org/x/sync/semaphore for weighted concurrency limits, uber-go/ratelimit for leaky-bucket rate limiting.

  • I/O & networking: os, io, bufio (always wrap raw io.Reader/Writer for line-oriented or hot-path code), io/fs (1.16+ — abstract FS interface, basis for embed.FS), os/exec (subprocesses), net (TCP/UDP/Unix sockets), net/http (production-grade server + client in stdlib), crypto/tls, database/sql. net/http.ServeMux got pattern matching with method + host + path wildcards in 1.22 (mux.HandleFunc("POST /users/{id}", h)). Context propagation: every long-running call should accept and check ctx.Done().

  • Stdlib highlights: context (deadlines / cancellation propagation; idiomatic first arg), encoding/json (slow but ubiquitous — json.RawMessage for lazy parsing; struct tags json:"name,omitempty" control marshal), encoding/json/v2 (1.25+ experimental — faster, cleaner API, opt-in), time (time.Time with monotonic clock; time.Tick, time.After, time.NewTicker), log/slog (structured logging, 1.21+ — JSON or text handlers; slog.Info("msg", "key", val)), slices/maps/cmp (1.21+ — generic utilities for slice/map ops), iter (1.23+ — iter.Seq[V]/iter.Seq2[K,V]), crypto/* (constant-time comparison, AEAD ciphers, ed25519), regexp (RE2-based, no PCRE backreferences), text/template/html/template (HTML version is context-aware-auto-escaping), embed (1.16+ — //go:embed assets/* bundles files into binary), testing (t.Run subtests, t.Parallel(), table-driven idioms).

5. Advanced

  • Memory & GC: generational-ish concurrent tri-color mark-sweep with write barrier; goal is sub-millisecond stop-the-world (typical pauses ~100μs-1ms in production). Tune via GOGC (target heap growth %, default 100 — meaning collect when heap doubles since last GC), GOMEMLIMIT (1.19+, soft memory ceiling — the runtime adjusts GC aggression to stay under). runtime/debug.SetGCPercent, SetMemoryLimit. GC pacer algorithm fully rewritten in 1.18 (Knuth’s exponentially weighted heap-growth model). The runtime returns memory to the OS via madvise(MADV_DONTNEED) (Linux) / MEM_DECOMMIT (Windows) after idle periods.
  • Concurrency deep dive: GMP scheduler — Goroutines (G) multiplexed onto Logical Processors (P, defaults to NumCPU), executed on OS threads (M). Work-stealing across P-local run queues + global queue. runtime.GOMAXPROCS controls P count (auto-respects cgroup CPU quotas since 1.25). Goroutines start with 8 KiB stack, grow as needed (was segmented stacks pre-1.3, now contiguous with copy-on-grow). runtime.LockOSThread for cgo / GUI loops. Asynchronous preemption (signal-based, 1.14+) prevents loop-without-function-call from monopolizing a P. Race detector: go run -race (built on ThreadSanitizer, 5-10x slowdown but invaluable in CI). goleak (Uber) detects leaked goroutines in tests.
  • FFI/interop: cgo (import "C" + // #include <foo.h> comment). Crossing the cgo boundary is expensive (~150-200 ns each way pre-1.21, ~80 ns post optimizations) and breaks goroutine scheduling on that thread (the M is parked). Avoid in hot loops. go:linkname to access unexported symbols (1.22 added restrictions), go:noescape to lie about pointer escape, go:nosplit to skip stack growth check (kernel/runtime only). WebAssembly target: GOOS=js GOARCH=wasm for browsers, GOOS=wasip1 GOARCH=wasm (1.21+, WASI Preview 1) for serverless edge (Fastly Compute, fermyon Spin, wasmtime). Component Model + WASI 0.2 support landing through 2025-26.
  • Reflection: reflect package — reflect.TypeOf, reflect.ValueOf, Kind(). Used by encoding/json, text/template. Slow (10-100x slower than direct access); codegen via go generate is preferred for hot paths. encoding/json/v2 experimental (1.25+) is faster and addresses the long list of v1 footguns; opt-in via GOEXPERIMENT=jsonv2.
  • Performance tooling: pprof (CPU, heap, allocs, block, mutex, goroutine profiles via net/http/pprof HTTP endpoint), go tool trace (execution traces, see scheduler behavior, GC events, syscalls), go test -bench + testing.B, -race, -cpuprofile/-memprofile/-blockprofile/-mutexprofile, go test -gcflags='-m -m' for escape-analysis output (see what allocates), benchstat (golang.org/x/perf) for statistical benchmark comparison, perf-tool for Linux perf integration. PGO (1.21+ stable): go build -pgo=default.pgo — auto-picks default.pgo next to main package. Typical 2-7% wins, up to 14% on interpreter loops. Continuous Profiling stacks: Pyroscope / Grafana Phlare, Polar Signals, Datadog Continuous Profiler, all support Go pprof natively.

6. God mode

  • Assembly: Go’s own asm dialect (Plan 9 derived); per-arch files like foo_amd64.s. Used in runtime, crypto, math/big for SIMD.
  • Runtime scheduler internals: GMP, run queues (per-P local + global), netpoller (epoll/kqueue/IOCP), sysmon thread, preemption (signal-based since 1.14). Read runtime/proc.go.
  • Escape analysis: decides stack vs heap. Inspect with go build -gcflags='-m -m'. Make values escape (hide intent from compiler) only when necessary.
  • unsafe.Pointer rules: six legal conversion patterns (see unsafe docs). Anything else is unsafe and may break.
  • Build tags: //go:build linux && amd64 constraints; file-name suffixes (foo_linux_amd64.go).
  • plugin package: Linux/macOS only, ELF/Mach-O dlopen — fragile across Go versions, rarely used in practice.
  • Generics introspection: limited reflection on type parameters; code-shape-shared instantiation (GCShape) means one binary, runtime dictionary lookup.
  • Race detector internals: built on ThreadSanitizer (TSan) with happens-before tracking via vector clocks.
  • Compiler directives: //go:noescape, //go:linkname, //go:noinline, //go:nosplit, //go:nowritebarrier, //go:embed (1.16+), //go:generate.
  • Profile-guided optimization: default.pgo next to main package auto-detected (1.21+). Typical 2-7% wins.
  • Embedding the runtime: Go can be embedded into other languages via c-shared/c-archive build modes (go build -buildmode=c-shared).
  • TinyGo for microcontrollers / WASM with much smaller runtime; uses LLVM backend.

7. Idioms & style

  • Naming: MixedCaps (PascalCase) for exported, mixedCaps for unexported. Acronyms uppercase: URL, ID, HTTP. Short receiver names (r *Rect). Package names lowercase, single word, no underscores.
  • Formatter: gofmt (and goimports) — non-negotiable; CI rejects unformatted code. Linter: go vet (built-in), staticcheck (the canonical extra linter), golangci-lint (aggregator).
  • Idiomatic Go: “accept interfaces, return structs”; small interfaces (often single-method like io.Reader); errors are values, handle them at every layer; one-letter-ish receiver names; explicit if err != nil; favor composition over inheritance (Go has no inheritance); use channels to share memory by communicating, not memory to share state. Effective Go is the canonical doc.
  • Reviewers look for: unhandled errors, mutex without defer Unlock, goroutine leaks (no cancellation), unbuffered channel deadlocks, context.Context not first param, package-level mutable state, premature abstraction.

8. Ecosystem

  • Web: stdlib net/http (production-grade; got method+host routing in 1.22). Higher-level: Gin (most popular, fast), Echo, Fiber (fasthttp-based, fastest), Chi (idiomatic, stdlib-style), Gorilla (originally archived 2022, revived as community project), Huma (OpenAPI-first), Encore (full-stack framework with built-in infra), Buffalo, Iris, Beego, Revel. gRPC-Go, connectrpc/connect-go (Bufbuild — schema-first, gRPC/Connect/JSON), Twirp (Twitch), Goa (design-first).
  • DB: database/sql + drivers (pgx — Postgres native, faster than database/sql wrapper for hot paths; go-sql-driver/mysql; mattn/go-sqlite3; microsoft/go-mssqldb). ORMs: GORM (most popular), sqlc (codegen from SQL — type-safe, idiomatic, generates per-query funcs), ent (Facebook, graph schema), bun (ex-go-pg author), Pop (Buffalo), sqlboiler (codegen from DB schema), squirrel (query builder).
  • Migration: golang-migrate, goose, atlas (HCL/SQL declarative).
  • CLI: Cobra (kubectl/hugo standard, GitHub CLI uses it), Viper (config — env, file, flags merged), urfave/cli, kong, mitchellh/cli, kingpin.
  • Logging/observability: log/slog (stdlib structured logging, 1.21+), uber-go/zap (fastest), zerolog, logrus (deprecated). OpenTelemetry-Go, Prometheus client lib, Datadog APM, Honeycomb beeline.
  • Cloud/infra (Go’s empire): Kubernetes, Terraform, Docker, containerd, etcd, CoreDNS, Prometheus, Grafana Loki/Mimir/Tempo, Cilium (eBPF networking), Istio, Linkerd, HashiCorp Vault, Consul, Nomad, Boundary, Caddy (HTTPS-by-default web server), Traefik, Helm, ArgoCD, Flux, Tekton, Buildkite agent, Drone CI, GoReleaser, Pulumi (Go also a target language), Buf (Protobuf tooling), gRPC Go server, NATS, MinIO, eBPF tooling (Cilium/Tetragon). Roughly 60-70% of CNCF graduated projects are written in Go.
  • Testing: stdlib testing + testing/quick; testify (assertions/mocks, near-ubiquitous), gomock (replaces deprecated golang/mock), counterfeiter, gotestsum (nicer output + JUnit XML), gomega/ginkgo (BDD), rapid (property-based), go-cmp (Google, structured diff), sqlmock, httptest (stdlib HTTP fakes), mockery, Testcontainers-Go.
  • Performance/tracing: pprof (stdlib + UI in go tool pprof), go tool trace, delve debugger (dlv — IDE integration via DAP), objdump/nm/addr2line (go tool), benchstat (statistical bench compare), flamegraph.pl + pprof.
  • Code generation / interop: buf (Protobuf), stringer (enum String()), wire (Google, compile-time DI), mockgen, swag (Swagger), goa, gqlgen (GraphQL server), rod/chromedp (headless Chrome).
  • Embedded / cross-target: TinyGo (LLVM-based, microcontrollers, WASM with much smaller runtime), Gobot (robotics).
  • Doc tools: go doc + pkg.go.dev (auto-published from Git tags, indexed via the Go module proxy).
  • Notable users: Google (internal infra, gVisor, Vitess, Bazel), Cloudflare (most internal services), Uber, Twitch, Dropbox (replaced some Python services), Netflix (Spinnaker), MongoDB, Meta, Microsoft (Azure components, GitHub including Actions runner), HashiCorp (entire product line), Snowflake, Stripe, American Express, Riot Games, Twitter, Datadog (agents), New Relic. Used to write 77% of CNCF projects as of 2024.

Concurrency patterns cookbook

// Fan-out, fan-in with errgroup (golang.org/x/sync/errgroup)
g, ctx := errgroup.WithContext(ctx)
results := make([]Result, len(jobs))
for i, job := range jobs {
    i, job := i, job  // capture; not needed in Go 1.22+ but explicit is fine
    g.Go(func() error {
        r, err := process(ctx, job)
        if err != nil { return err }
        results[i] = r
        return nil
    })
}
if err := g.Wait(); err != nil { return err }
 
// Concurrency limit with semaphore
sem := semaphore.NewWeighted(8)
for _, job := range jobs {
    if err := sem.Acquire(ctx, 1); err != nil { return err }
    go func(j Job) {
        defer sem.Release(1)
        process(j)
    }(job)
}
 
// Pipeline with done-channel cancellation
gen := func(ctx context.Context, nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for _, n := range nums {
            select {
            case <-ctx.Done(): return
            case out <- n:
            }
        }
    }()
    return out
}
 
// Context propagation
ctx, cancel := context.WithTimeout(parentCtx, 5*time.Second)
defer cancel()
resp, err := http.NewRequestWithContext(ctx, "GET", url, nil)

Idiomatic patterns examples

// Constructor returning interface, value-returning struct
type Cache interface { Get(k string) (string, bool); Set(k, v string) }
type memCache struct { mu sync.RWMutex; m map[string]string }
func NewCache() Cache { return &memCache{m: make(map[string]string)} }
 
// Functional options
type ServerOption func(*Server)
func WithPort(p int) ServerOption { return func(s *Server) { s.port = p } }
func NewServer(opts ...ServerOption) *Server {
    s := &Server{port: 8080}
    for _, opt := range opts { opt(s) }
    return s
}
 
// HTTP handler with middleware
func logging(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        slog.Info("req", "method", r.Method, "path", r.URL.Path,
                  "duration", time.Since(start))
    })
}

9. Gotchas

  • nil interface vs interface holding typed nil: var p *Foo = nil; var i Foo = p; i == nil returns false. Source of constant bugs.
  • Loop variable capture (fixed in 1.22): before 1.22, for i := range xs { go func() { use(i) }() } shared i. Fixed: each iteration has its own.
  • Slices share backing arrays: s2 := s1[2:5] mutates s1. Append may or may not reallocate; rely on returned slice.
  • map iteration order is randomized; do not depend on order.
  • time.Now() includes monotonic clock; serializing across the wire loses it.
  • No generics for methods, only top-level functions and type definitions (1.21 still has limits).
  • defer in a long loop queues N callbacks; can OOM.
  • Goroutine leaks from forgotten <-ch receives or missing context.Done() selects.
  • init() runs at package load; ordering is per-file alphabetical, then deps; surprising side effects.
  • json.Marshal errors on cyclic types and unexported fields silently.
  • http.DefaultClient has no timeout — set one or you’ll wait forever.
  • new(T) vs &T{}: prefer &T{}; same effect, more idiomatic.
  • Capitalization controls visibility — rename a field from Name to name and you’ve broken JSON marshaling and your public API.
  • Empty interface{} (now any) is a runtime escape hatch — once a value enters any, you’ve lost compile-time type info; reflect is the only path back.
  • Channel direction confusionchan T (bidirectional), chan<- T (send-only), <-chan T (receive-only). Functions should take the narrowest channel direction needed.
  • Slice len vs cap surprisesmake([]T, 0, 10) gives a length-0, capacity-10 slice; append won’t reallocate until you push the 11th element.
  • go build honors GOPROXY / GOSUMDB / GONOPROXY / GOPRIVATE env vars — set GOPRIVATE=*.internal.corp to skip the public proxy + checksum DB for company-internal modules.
  • time.After leaks a goroutine until the timer fires — in select loops, use time.NewTimer + manual Stop().
  • select with default is non-blocking — frequently misused as a busy-loop polling pattern (burns CPU). Use a real timeout case.
  • panic across goroutines — a panic in goroutine A is not caught by recover in goroutine B. Every long-lived goroutine should defer-recover at its top level.

Modern Go (1.21 → 1.26)

VersionReleaseHighlights
1.21 (Aug 2023)LTS-ishlog/slog, slices, maps, cmp packages; PGO GA; min/max built-ins; clear; WASI preview
1.22 (Feb 2024)Loop variable fix (per-iteration scope); for i := range 10 (int range); net/http enhanced ServeMux (POST /users/{id}); rand/v2
1.23 (Aug 2024)Range-over-func iterators (for v := range myIter); iter pkg with Seq[V] / Seq2[K,V]; unique pkg for interning; telemetry opt-in
1.24 (Feb 2025)Generic type aliases; os.Root for FS confinement (no path traversal); weak pointers; tool dependencies in go.mod; FIPS 140-3 module
1.25 (Aug 2025)testing/synctest (deterministic concurrency tests); encoding/json/v2 experimental; GOMAXPROCS cgroup-aware default; container-aware GC; new GC experiment (Green Tea)
1.26 (Feb 2026)Continued v2 stdlib evolution, PGO improvements, scheduler tweaks

iter + range-over-func (1.23+) is the most user-visible change since generics — finally lets libraries expose lazy iteration ergonomically:

func Lines(r io.Reader) iter.Seq[string] {
    return func(yield func(string) bool) {
        sc := bufio.NewScanner(r)
        for sc.Scan() {
            if !yield(sc.Text()) { return }
        }
    }
}
 
for line := range Lines(f) { ... }

testing/synctest (1.25+) gives deterministic time control for concurrency tests — no more time.Sleep(50*time.Millisecond) flakiness.

Generics maturity (1.18 → 1.26)

Generics landed in 1.18 (Mar 2022). The 4 years since have smoothed edges:

// Type sets via interfaces
type Ordered interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 |
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
    ~float32 | ~float64 |
    ~string
}
 
func Max[T Ordered](xs ...T) T {
    if len(xs) == 0 { var zero T; return zero }
    m := xs[0]
    for _, x := range xs[1:] {
        if x > m { m = x }
    }
    return m
}

golang.org/x/exp/constraints ships Ordered, Integer, Float, Complex, Signed, Unsigned. The stdlib slices + maps + cmp packages (1.21+) use these throughout.

Known limitations (still in 1.26):

  • No generic methods on types (only top-level funcs + type definitions).
  • No type-parametric type aliases (until 1.24 — now supported).
  • comparable constraint covers most type-set comparison needs but ~ shows up often.

Testing patterns

Go’s testing package is minimalist. Idiomatic patterns:

// Table-driven tests
func TestParse(t *testing.T) {
    cases := []struct {
        name    string
        input   string
        want    int
        wantErr bool
    }{
        {"empty", "", 0, true},
        {"int", "42", 42, false},
        {"neg", "-7", -7, false},
        {"hex", "0x1F", 31, false},
        {"junk", "abc", 0, true},
    }
    for _, c := range cases {
        t.Run(c.name, func(t *testing.T) {
            got, err := Parse(c.input)
            if (err != nil) != c.wantErr {
                t.Fatalf("err = %v, wantErr %v", err, c.wantErr)
            }
            if got != c.want {
                t.Errorf("got %d, want %d", got, c.want)
            }
        })
    }
}
 
// Parallel subtests
func TestThing(t *testing.T) {
    t.Parallel()
    for _, c := range cases {
        c := c
        t.Run(c.name, func(t *testing.T) {
            t.Parallel()
            // ...
        })
    }
}
 
// Benchmarks
func BenchmarkParse(b *testing.B) {
    for b.Loop() {  // Go 1.24+: replaces "for i := 0; i < b.N; i++"
        _, _ = Parse("12345")
    }
}
 
// Fuzzing (1.18+)
func FuzzParse(f *testing.F) {
    f.Add("42"); f.Add("-7"); f.Add("0xFF")
    f.Fuzz(func(t *testing.T, s string) {
        _, _ = Parse(s)   // should never panic
    })
}

Run: go test ./... -v -race -cover, go test -bench=. -benchmem, go test -fuzz=FuzzParse -fuzztime=30s.

Real code examples

Chi HTTP server with middleware and structured logging

package main
 
import (
    "context"
    "encoding/json"
    "log/slog"
    "net/http"
    "os"
    "time"
 
    "github.com/go-chi/chi/v5"
    "github.com/go-chi/chi/v5/middleware"
)
 
type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}
 
func main() {
    logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
    slog.SetDefault(logger)
 
    r := chi.NewRouter()
    r.Use(middleware.RequestID)
    r.Use(middleware.RealIP)
    r.Use(middleware.Recoverer)
    r.Use(middleware.Timeout(15 * time.Second))
 
    r.Get("/users/{id}", getUser)
    r.Post("/users", createUser)
 
    srv := &http.Server{
        Addr:         ":8080",
        Handler:      r,
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
        IdleTimeout:  120 * time.Second,
    }
    slog.Info("listening", "addr", srv.Addr)
    if err := srv.ListenAndServe(); err != nil {
        slog.Error("server failed", "err", err)
        os.Exit(1)
    }
}
 
func getUser(w http.ResponseWriter, r *http.Request) {
    id := chi.URLParam(r, "id")
    user := User{ID: 1, Name: "Alice", Email: "a@x.com"}
    _ = id
    w.Header().Set("Content-Type", "application/json")
    _ = json.NewEncoder(w).Encode(user)
}
 
func createUser(w http.ResponseWriter, r *http.Request) {
    var u User
    if err := json.NewDecoder(r.Body).Decode(&u); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    u.ID = 42
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    _ = json.NewEncoder(w).Encode(u)
}

Graceful shutdown with context

func runWithSignal(ctx context.Context, srv *http.Server) error {
    ctx, stop := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)
    defer stop()
 
    errCh := make(chan error, 1)
    go func() { errCh <- srv.ListenAndServe() }()
 
    select {
    case <-ctx.Done():
        slog.Info("shutdown signal received")
        sCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
        defer cancel()
        return srv.Shutdown(sCtx)
    case err := <-errCh:
        return err
    }
}

Range-over-func iterator (Go 1.23+)

import "iter"
 
func KVs[K comparable, V any](m map[K]V) iter.Seq2[K, V] {
    return func(yield func(K, V) bool) {
        for k, v := range m {
            if !yield(k, v) { return }
        }
    }
}
 
// Caller:
for k, v := range KVs(myMap) {
    fmt.Println(k, v)
}

Real-world Go service performance (2026)

  • A minimal net/http “hello world” with stdlib mux: ~250k req/s on a single-core box, ~5M on 32 cores.
  • Fiber / fasthttp-based: 1.5-2x faster on hello-world due to avoiding net/http allocations.
  • gRPC-Go: ~100k unary RPC/s/core; streaming much higher.
  • Typical containerized microservice: <30 MB resident, <5ms p99 idle, JIT-less so cold-start is instant (~50-100ms incl. TLS).
  • Goroutines: spawning 1M is a couple GB of resident memory and feasible; the GMP scheduler stays cool.
  • GC pauses: typically <500μs on healthy heaps; with GOMEMLIMIT set close to container limit, allocator backpressure replaces OOM.

Structured logging + error chains (1.21+)

log/slog (Go 1.21) replaced the era of “everyone runs zap/zerolog separately.” The stdlib ships a high-performance structured logger with JSON + text handlers and context.Context propagation.

package main
 
import (
    "context"
    "errors"
    "fmt"
    "log/slog"
    "os"
)
 
type contextKey string
const requestIDKey contextKey = "request_id"
 
// Custom handler that pulls request_id from ctx into every log line
type ctxHandler struct{ slog.Handler }
func (h ctxHandler) Handle(ctx context.Context, r slog.Record) error {
    if v, ok := ctx.Value(requestIDKey).(string); ok {
        r.AddAttrs(slog.String("request_id", v))
    }
    return h.Handler.Handle(ctx, r)
}
 
var ErrNotFound = errors.New("not found")
 
type DBError struct { Query string; Err error }
func (e *DBError) Error() string  { return fmt.Sprintf("db: %s: %v", e.Query, e.Err) }
func (e *DBError) Unwrap() error  { return e.Err }
 
func loadUser(ctx context.Context, id int) (any, error) {
    dbErr := &DBError{Query: "SELECT users", Err: ErrNotFound}
    return nil, fmt.Errorf("loadUser(%d): %w", id, dbErr)   // %w preserves chain
}
 
func main() {
    h := ctxHandler{slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})}
    slog.SetDefault(slog.New(h))
 
    ctx := context.WithValue(context.Background(), requestIDKey, "req-abc-123")
 
    _, err := loadUser(ctx, 42)
    if err != nil {
        if errors.Is(err, ErrNotFound) {           // unwraps chain to find sentinel
            slog.WarnContext(ctx, "user not found", "err", err)
        }
        var dbErr *DBError
        if errors.As(err, &dbErr) {                 // pulls typed error from chain
            slog.ErrorContext(ctx, "db failure", "query", dbErr.Query, "err", err)
        }
    }
 
    combined := errors.Join(ErrNotFound, fmt.Errorf("backup also failed"))   // 1.20+
    slog.Error("multi-failure", "err", combined)
}

slog.Logger.With(...) returns a logger pre-seeded with attrs — per-request or per-component loggers without recomputing fields.

Generics deep-dive — when they help, when they don’t

Generics landed in 1.18; four years in, patterns have converged.

Where generics shine — type-set constraints + parametric helpers:

type SignedInt interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 }
type Numeric interface {
    SignedInt | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~float32 | ~float64
}
 
func Sum[T Numeric](xs []T) T {
    var s T
    for _, x := range xs { s += x }
    return s
}
 
func Map[T, U any](xs []T, f func(T) U) []U {
    out := make([]U, len(xs))
    for i, x := range xs { out[i] = f(x) }
    return out
}
 
func GroupBy[K comparable, V any](xs []V, keyFn func(V) K) map[K][]V {
    out := map[K][]V{}
    for _, x := range xs { out[keyFn(x)] = append(out[keyFn(x)], x) }
    return out
}
 
// Type-parameter inference (1.21+) — call site doesn't need explicit T
doubled := Map([]int{1, 2, 3}, func(n int) int { return n * 2 })  // T, U inferred

Where generics DON’T help — interface satisfaction is cleaner:

// Bad: forcing generics where an interface is obvious
func Print[T fmt.Stringer](x T) { fmt.Println(x.String()) }
 
// Better: just take the interface
func Print(x fmt.Stringer) { fmt.Println(x.String()) }

Rule of thumb: if it would compile with any + a type assertion, use an interface. If the function needs the static return type to be T, use generics.

Limitations as of 1.26:

  • No generic methods on types — only top-level funcs + type definitions take type parameters.
  • GCShape instantiation — compiler shares one instantiation per “GC shape” (size class + pointer layout). Small runtime dictionary lookup in generic code; usually invisible.
  • No higher-kinded types — can’t express Functor[F[_]].
  • Generic type aliases — finally added in 1.24 (type Set[T comparable] = map[T]struct{}).

Fuzz + property + golden-file testing

// Fuzz testing (1.18+)
func FuzzMarshal(f *testing.F) {
    f.Add([]byte(`{"x":1}`))
    f.Add([]byte(`{"x":1,"y":2}`))
    f.Fuzz(func(t *testing.T, data []byte) {
        var v map[string]any
        if err := json.Unmarshal(data, &v); err != nil { return }
        out, err := json.Marshal(v)
        if err != nil { t.Fatalf("round-trip: %v", err) }
        var back map[string]any
        if err := json.Unmarshal(out, &back); err != nil { t.Fatalf("re-unmarshal: %v", err) }
    })
}
// Run: go test -fuzz=FuzzMarshal -fuzztime=60s
 
// Property-based with pgregory.net/rapid
import "pgregory.net/rapid"
func TestReverseProp(t *testing.T) {
    rapid.Check(t, func(t *rapid.T) {
        xs := rapid.SliceOf(rapid.Int()).Draw(t, "xs")
        if !slices.Equal(xs, reverse(reverse(xs))) {
            t.Fatalf("double-reverse changed slice")
        }
    })
}
 
// Golden file with go-cmp
import "github.com/google/go-cmp/cmp"
func TestRender(t *testing.T) {
    got := Render(input)
    goldenPath := "testdata/expected.json"
    if *update { os.WriteFile(goldenPath, got, 0644); return }
    want, _ := os.ReadFile(goldenPath)
    if diff := cmp.Diff(string(want), string(got)); diff != "" {
        t.Errorf("output mismatch (-want +got):\n%s", diff)
    }
}

Combine with testcontainers-go for ephemeral Postgres / Redis / Kafka, net/http/httptest for HTTP fakes, sqlmock for DB mocks, uber-go/mock (maintained gomock fork) for interfaces. gotestsum produces JUnit XML for CI.

Production observability (OpenTelemetry-Go)

CNCF-standard tracing + metrics + logging recipe for Go services in 2026:

import (
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
    "go.opentelemetry.io/otel/sdk/resource"
    sdktrace "go.opentelemetry.io/otel/sdk/trace"
    semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
    "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
 
func initTracing(ctx context.Context) (func(context.Context) error, error) {
    exporter, err := otlptracehttp.New(ctx, otlptracehttp.WithEndpoint("otel-collector:4318"))
    if err != nil { return nil, err }
    tp := sdktrace.NewTracerProvider(
        sdktrace.WithBatcher(exporter),
        sdktrace.WithResource(resource.NewWithAttributes(semconv.SchemaURL,
            semconv.ServiceName("api"), semconv.ServiceVersion("1.2.3"))),
    )
    otel.SetTracerProvider(tp)
    return tp.Shutdown, nil
}
 
// Auto-instrument HTTP with otelhttp
mux := http.NewServeMux()
mux.Handle("/api/", otelhttp.NewHandler(apiHandler, "api"))
 
// Manual span (propagates trace context via ctx)
tracer := otel.Tracer("github.com/me/api")
ctx, span := tracer.Start(ctx, "loadUser")
defer span.End()
span.SetAttributes(attribute.Int("user.id", id))

W3C headers (traceparent, tracestate) auto-propagate across services. Vendor exporters: Datadog Go SDK, Honeycomb beeline-go, New Relic Go agent, Grafana Tempo + Pyroscope, Jaeger native, OTLP for any CNCF backend.

Distributed Go workloads (gRPC + ConnectRPC + middleware)

ConnectRPC (Buf) has overtaken vanilla gRPC for new projects — speaks gRPC, gRPC-Web, and JSON over HTTP/1.1+2 with the same generated code.

// service.proto compiled with `buf generate`
type userServer struct{}
func (s *userServer) GetUser(ctx context.Context, req *connect.Request[userv1.GetUserRequest]) (*connect.Response[userv1.GetUserResponse], error) {
    return connect.NewResponse(&userv1.GetUserResponse{
        Id: req.Msg.Id, Name: "Alice",
    }), nil
}
 
func main() {
    mux := http.NewServeMux()
    mux.Handle(userv1connect.NewUserServiceHandler(&userServer{}))
 
    srv := &http.Server{
        Addr:         ":8080",
        Handler:      h2c.NewHandler(mux, &http2.Server{}),  // HTTP/2 over plaintext
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 30 * time.Second,
    }
    runWithGracefulShutdown(srv)
}
 
// Graceful shutdown — refuse new conns, drain in-flight
func runWithGracefulShutdown(srv *http.Server) {
    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
    defer stop()
    errCh := make(chan error, 1)
    go func() { errCh <- srv.ListenAndServe() }()
    select {
    case <-ctx.Done():
        slog.Info("draining connections")
        sCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
        defer cancel()
        srv.Shutdown(sCtx)
    case err := <-errCh:
        slog.Error("server died", "err", err)
    }
}

Alternatives: Twirp (Twitch, JSON-friendly), go-kit (microservices toolkit), go-zero (Chinese ecosystem, batteries-included). For canary deploys: systemd socket activation lets a new process inherit the listener FD via coreos/go-systemd/activation, old process drains and exits — zero-downtime without a load balancer.

CNCF Go ecosystem (Go’s empire)

Go is the dominant CNCF language. As of 2026, ~70% of CNCF graduated + incubating projects are Go-based:

LayerProjects
OrchestrationKubernetes, OpenShift, k3s, k0s, RKE2
Container runtimecontainerd, CRI-O, runc, youki (Rust exception)
Service meshIstio control plane, Consul Connect, Cilium service mesh
NetworkingCilium (eBPF), Calico, Flannel, MetalLB, Submariner
CI/CDArgoCD, Flux, Tekton, Jenkins X, KubeVela
ObservabilityPrometheus, Grafana core, Loki, Mimir, Tempo, Pyroscope, Jaeger, OpenTelemetry Collector
Storageetcd, MinIO, Rook, Longhorn, OpenEBS, Velero
Service discoveryConsul, CoreDNS
API gatewayTraefik, Emissary, Envoy Gateway control plane
MessagingNATS, CloudEvents SDK
PolicyOPA (Open Policy Agent), Kyverno, Falco
WorkflowArgo Workflows, Temporal (Go SDK first-class), Cadence
SecurityTrivy, Notary, sigstore/cosign, Falco, Tetragon
Web serversCaddy (HTTPS by default), Traefik
IaCTerraform, Pulumi (Go is a target lang), Crossplane

The combination of single-binary deploys, fast cross-compilation, small static binaries, lightweight goroutines for fan-out, and a stdlib handling HTTP/TLS/JSON without deps — is exactly what infra code needs.

10. Citations