Go Handbook

Go (Golang) is a statically typed, garbage-collected language designed at Google by Robert Griesemer, Rob Pike, and Ken Thompson, released in 2009. It was explicitly designed to solve the pain points of large-scale software engineering: slow C++ build times, complex dependency management, and difficult concurrency. Go compiles to a single statically linked binary, ships a strong standard library (HTTP, JSON, crypto, SQL, testing — all built in), and has a built-in concurrency model based on goroutines and channels (CSP). Docker, Kubernetes, Terraform, Prometheus, and CockroachDB are written in Go.

Pick Go when

  • Network services and microservices — Go's stdlib HTTP server is production-grade. Goroutines are cheap (2 KB stack) so you can handle 100k+ concurrent connections on a single box without async/await complexity.
  • CLI tools and DevOps tooling — Go produces a single, cross-compiled binary with no runtime dependency. Distributing a Go tool is trivially easy. kubectl, Terraform, and gh are all Go CLIs.
  • Cloud infrastructure — Kubernetes, Docker, Containerd, etcd, Prometheus, and most cloud-native projects are in Go. If you're building infrastructure tooling, Go is the default.
  • Teams that value simplicity and code review speed — Go has a small language spec, strict formatting (gofmt), and opinionated tooling. PRs are easier to review because there is usually one idiomatic way to do something.
  • Fast iteration on backend services — compilation is near-instant (seconds for large projects), tests run in parallel by default, and the tooling (go test, go build, go vet) works out of the box.

Think twice before choosing Go when

  • You need rich generics or functional patterns — Go added generics in 1.18 but the implementation is deliberately constrained. No higher-kinded types, no sum types, no pattern matching. Haskell, Scala, or Rust are better for type-level programming.
  • GUI or frontend applications — Go has no mature native GUI framework. Use Flutter/Dart, Swift, or Kotlin for desktop/mobile.
  • Data science or ML — the ecosystem is thin. Use Python.
  • Hard real-time or embedded — Go has a GC with sub-millisecond pauses (modern GOGC), but pauses are not zero. For hard real-time (medical devices, motor control), use C, Rust, or Ada.
  • Error handling fatigue — Go's explicit if err != nil pattern scales well but is verbose. Rust's ? operator and TypeScript's union types are terser for error-heavy code.

Go vs. its closest alternatives

  • Go vs Rust — Rust is faster (no GC) and memory-safe at compile time. Go is far easier to learn and faster to ship. Go for services; Rust for systems programming and maximum efficiency.
  • Go vs Python — Go is 10–50× faster, statically typed, and compiles to a single binary. Python is more expressive and has a much larger ecosystem. Go for production services; Python for scripting, ML, and rapid prototyping.
  • Go vs Java/C# — Go has faster startup, a smaller binary, and simpler concurrency. Java and C# have larger ecosystems, richer type systems, and decades of enterprise library support. Go for new cloud services; Java/C# for enterprise or heavily framework-dependent applications.

Resources

Topics

Variables & Constants

go
package main

// var declaration — explicit type
var name string = 'Alice'
var age  int    = 30

// var block
var (
    host string = 'localhost'
    port int    = 8080
)

// Short variable declaration (:=) — inside functions only
func main() {
    x := 42           // int inferred
    y := 3.14         // float64 inferred
    ok := true        // bool
    s := 'hello'      // string

    // Multiple assignment
    a, b := 1, 2
    a, b = b, a       // swap

    // Blank identifier — discard values
    val, _ := strconv.Atoi('42')

    // Zero values (default when declared without initializer)
    var i int       // 0
    var f float64   // 0.0
    var b2 bool     // false
    var str string  // ''
    var p *int      // nil
    _ = i; _ = f; _ = b2; _ = str; _ = p; _ = val
}

// Constants
const Pi  = 3.14159
const Msg = 'hello'

// Typed constants
const MaxSize int = 1024

// iota — auto-incrementing constant generator
type Direction int
const (
    North Direction = iota // 0
    East                   // 1
    South                  // 2
    West                   // 3
)

type ByteSize float64
const (
    _           = iota              // ignore first value
    KB ByteSize = 1 << (10 * iota) // 1 << 10  = 1024
    MB                              // 1 << 20
    GB                              // 1 << 30
)

Types & Aliases

go
package main

import 'fmt'

// Basic types
// bool, string
// int  int8  int16  int32  int64
// uint uint8 uint16 uint32 uint64 uintptr
// byte (alias for uint8), rune (alias for int32)
// float32 float64
// complex64 complex128

// Type alias — same underlying type, different name
type Celsius    float64
type Fahrenheit float64

func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }

// Type definition — new distinct type
type UserID int64
type Email  string

// Explicit type conversion (no implicit coercion)
var i int = 42
var f float64 = float64(i)   // must convert
var u uint = uint(f)
_ = u

// Type assertions — extract concrete type from interface
var val interface{} = 'hello'
s, ok := val.(string)   // safe assertion; ok = true
if ok {
    fmt.Println(s)
}

// Type switch
func describe(i interface{}) string {
    switch v := i.(type) {
    case int:
        return fmt.Sprintf('int: %d', v)
    case string:
        return fmt.Sprintf('string: %s', v)
    case bool:
        return fmt.Sprintf('bool: %t', v)
    default:
        return fmt.Sprintf('unknown: %T', v)
    }
}

Control Flow

go
package main

import 'fmt'

func control() {
    // if — no parens required
    x := 10
    if x > 5 {
        fmt.Println('big')
    } else if x == 5 {
        fmt.Println('five')
    } else {
        fmt.Println('small')
    }

    // if with init statement — scopes variable to block
    if n, err := fmt.Println('hi'); err != nil {
        _ = n
    }

    // for — the only loop keyword in Go
    for i := 0; i < 5; i++ {
        fmt.Println(i)
    }

    // while-style
    n := 0
    for n < 3 {
        n++
    }

    // infinite loop
    // for { }

    // range — iterate slices, maps, strings, channels
    nums := []int{10, 20, 30}
    for i, v := range nums {
        fmt.Printf('index=%d val=%d\n', i, v)
    }
    for _, v := range nums { _ = v }   // skip index
    for i := range nums   { _ = i }   // skip value

    m := map[string]int{'a': 1, 'b': 2}
    for k, v := range m {
        fmt.Printf('%s=%d\n', k, v)
    }

    // range over string yields runes
    for i, r := range 'héllo' {
        fmt.Printf('%d: %c\n', i, r)
    }

    // switch — no fallthrough by default
    day := 'Mon'
    switch day {
    case 'Mon', 'Tue', 'Wed', 'Thu', 'Fri':
        fmt.Println('weekday')
    case 'Sat', 'Sun':
        fmt.Println('weekend')
    default:
        fmt.Println('unknown')
    }

    // switch with no expression — replaces if-else chains
    score := 75
    switch {
    case score >= 90:
        fmt.Println('A')
    case score >= 70:
        fmt.Println('B')
    default:
        fmt.Println('C')
    }

    // defer — runs when surrounding function returns (LIFO order)
    defer fmt.Println('world')
    fmt.Println('hello')

    // defer with loop — capture value immediately
    for i := 0; i < 3; i++ {
        i := i   // shadow to capture current value
        defer fmt.Println(i)
    }

    // break / continue with labels
outer:
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            if j == 1 {
                continue outer
            }
        }
    }
}

Functions

go
package main

import (
    'errors'
    'fmt'
)

// Basic function
func add(a, b int) int {
    return a + b
}

// Multiple return values
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New('division by zero')
    }
    return a / b, nil
}

// Named return values — usable but avoid for clarity
func minmax(nums []int) (min, max int) {
    min, max = nums[0], nums[0]
    for _, n := range nums[1:] {
        if n < min { min = n }
        if n > max { max = n }
    }
    return   // bare return uses named values
}

// Variadic function — last param can receive zero or more values
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

// Spread a slice into variadic param
func main() {
    fmt.Println(sum(1, 2, 3))
    nums := []int{4, 5, 6}
    fmt.Println(sum(nums...))   // spread

    // First-class functions
    double := func(n int) int { return n * 2 }
    fmt.Println(double(5))

    // Closure — captures variables from enclosing scope
    counter := func() func() int {
        n := 0
        return func() int {
            n++
            return n
        }
    }()
    fmt.Println(counter(), counter())   // 1 2

    // Functions as arguments
    apply := func(f func(int) int, x int) int { return f(x) }
    fmt.Println(apply(double, 7))

    // Immediately invoked function expression
    result := func(x int) int { return x * x }(9)
    fmt.Println(result)
}

// panic / recover
func safeDivide(a, b int) (result int, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf('recovered: %v', r)
        }
    }()
    if b == 0 {
        panic('divide by zero')
    }
    return a / b, nil
}

Pointers

go
package main

import 'fmt'

func increment(p *int) {
    *p++   // dereference and mutate
}

func newInt(n int) *int {
    return &n   // safe: Go variables escape to heap when needed
}

func main() {
    x := 42
    p := &x        // p is *int, holds address of x
    fmt.Println(*p) // dereference: prints 42

    *p = 100
    fmt.Println(x)  // x is now 100

    increment(&x)
    fmt.Println(x)  // 101

    // new() — allocates zeroed value, returns pointer
    q := new(int)   // *int pointing to 0
    *q = 7
    fmt.Println(*q)

    // nil pointer — zero value for pointer types
    var ptr *string
    fmt.Println(ptr == nil)   // true

    // Pointer to struct — Go auto-dereferences for fields
    type Point struct{ X, Y int }
    pt := &Point{1, 2}
    pt.X = 10   // equivalent to (*pt).X = 10
    fmt.Println(*pt)

    // Pointer comparison
    a := 1
    b := 1
    pa, pb := &a, &b
    fmt.Println(pa == pb)   // false — different addresses
    fmt.Println(*pa == *pb) // true  — same value
}

Structs & Methods

go
package main

import (
    'fmt'
    'math'
)

// Struct definition
type Point struct {
    X, Y float64
}

// Methods — defined outside struct with a receiver
func (p Point) Distance() float64 {
    return math.Sqrt(p.X*p.X + p.Y*p.Y)
}

// Pointer receiver — can mutate, preferred when struct is large
func (p *Point) Scale(factor float64) {
    p.X *= factor
    p.Y *= factor
}

// Struct literals
func main() {
    p1 := Point{X: 3, Y: 4}
    p2 := Point{3, 4}        // positional (fragile, avoid for exported)
    p3 := Point{}            // zero value: {0, 0}
    _ = p2; _ = p3

    fmt.Println(p1.Distance())
    p1.Scale(2)
    fmt.Println(p1)

    // Anonymous struct
    person := struct {
        Name string
        Age  int
    }{'Bob', 30}
    fmt.Println(person)
}

// Struct embedding — promotes methods and fields
type Animal struct {
    Name string
}
func (a Animal) Speak() string { return a.Name + ' makes a sound' }

type Dog struct {
    Animal         // embedded (not named)
    Breed string
}
func (d Dog) Speak() string { return d.Name + ' barks' }   // override

// Composition with explicit delegation
type Logger struct{ prefix string }
func (l Logger) Log(msg string) { fmt.Printf('[%s] %s\n', l.prefix, msg) }

type Server struct {
    Logger          // promoted: server.Log() works
    host string
}

// Struct tags — used by encoding/json, database/sql, etc.
type User struct {
    ID    int    `json:'id'    db:'user_id'`
    Name  string `json:'name'  db:'user_name'`
    Email string `json:'email' db:'-'`
}

Interfaces

go
package main

import (
    'fmt'
    'math'
)

// Interface — defined by method set
type Shape interface {
    Area() float64
    Perimeter() float64
}

type Circle struct{ Radius float64 }
type Rect   struct{ Width, Height float64 }

// Implicit implementation — no 'implements' keyword
func (c Circle) Area()      float64 { return math.Pi * c.Radius * c.Radius }
func (c Circle) Perimeter() float64 { return 2 * math.Pi * c.Radius }

func (r Rect) Area()      float64 { return r.Width * r.Height }
func (r Rect) Perimeter() float64 { return 2 * (r.Width + r.Height) }

func printShape(s Shape) {
    fmt.Printf('area=%.2f perim=%.2f\n', s.Area(), s.Perimeter())
}

func main() {
    shapes := []Shape{
        Circle{5},
        Rect{3, 4},
    }
    for _, s := range shapes {
        printShape(s)
    }

    // Interface variable holds (value, type) pair
    var s Shape = Circle{1}
    fmt.Printf('%T %v\n', s, s)

    // Empty interface — accepts any value (use sparingly)
    var any interface{} = 42
    any = 'now a string'
    _ = any

    // any alias (Go 1.18+)
    var v any = true
    _ = v

    // Interface embedding
    type Reader interface { Read(p []byte) (int, error) }
    type Writer interface { Write(p []byte) (int, error) }
    type ReadWriter interface {
        Reader
        Writer
    }

    // Stringer interface from fmt package
    // implementing String() string makes fmt.Println use it
}

// Nil interface pitfall
type MyError struct{ msg string }
func (e *MyError) Error() string { return e.msg }

func bad() error {
    var e *MyError = nil
    return e   // returns non-nil interface holding nil *MyError!
}
func good() error {
    return nil   // truly nil interface
}

Goroutines

go
package main

import (
    'fmt'
    'sync'
    'time'
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done()   // signals completion
    fmt.Printf('worker %d starting\n', id)
    time.Sleep(50 * time.Millisecond)
    fmt.Printf('worker %d done\n', id)
}

func main() {
    // go keyword launches goroutine — lightweight, ~2 KB stack
    go func() {
        fmt.Println('async')
    }()

    // WaitGroup — wait for multiple goroutines
    var wg sync.WaitGroup

    for i := 1; i <= 5; i++ {
        wg.Add(1)              // increment before launching
        go worker(i, &wg)     // pass pointer to wg
    }
    wg.Wait()                  // block until all Done()

    fmt.Println('all workers done')

    // Goroutines share address space — need synchronization
    // for shared data (use channels or sync primitives)

    // Goroutine leak — avoid launching without a way to stop
    // Always ensure goroutines can exit (via channel/context)

    // GOMAXPROCS — number of OS threads (default = NumCPU)
    // runtime.GOMAXPROCS(4)
}

Channels

go
package main

import (
    'fmt'
    'time'
)

func main() {
    // Unbuffered channel — send blocks until receive (synchronous)
    ch := make(chan int)

    go func() {
        ch <- 42   // send
    }()
    v := <-ch       // receive
    fmt.Println(v)

    // Buffered channel — send blocks only when buffer full
    bch := make(chan string, 3)
    bch <- 'a'
    bch <- 'b'
    bch <- 'c'
    fmt.Println(<-bch, <-bch, <-bch)

    // Close channel — signals no more values
    nums := make(chan int, 5)
    for i := 0; i < 5; i++ { nums <- i }
    close(nums)

    // Range over closed channel — drains then stops
    for n := range nums {
        fmt.Println(n)
    }

    // Check if channel closed
    ch2 := make(chan int, 1)
    ch2 <- 7
    close(ch2)
    v2, ok := <-ch2
    fmt.Printf('v=%d ok=%t\n', v2, ok)   // v=7 ok=true
    v3, ok := <-ch2
    fmt.Printf('v=%d ok=%t\n', v3, ok)   // v=0 ok=false

    // select — multiplex channel operations
    c1 := make(chan string, 1)
    c2 := make(chan string, 1)
    c1 <- 'one'
    c2 <- 'two'

    select {
    case msg := <-c1:
        fmt.Println('c1:', msg)
    case msg := <-c2:
        fmt.Println('c2:', msg)
    }

    // select with default — non-blocking
    ch3 := make(chan int)
    select {
    case v := <-ch3:
        fmt.Println('got', v)
    default:
        fmt.Println('no value ready')
    }

    // Timeout pattern
    result := make(chan int, 1)
    go func() {
        time.Sleep(100 * time.Millisecond)
        result <- 1
    }()
    select {
    case r := <-result:
        fmt.Println('result:', r)
    case <-time.After(50 * time.Millisecond):
        fmt.Println('timed out')
    }

    // Directional channel types — enforce ownership
    // chan<- int   send-only
    // <-chan int   receive-only
    ping := func(ch chan<- string) { ch <- 'ping' }
    pong := func(ch <-chan string) { fmt.Println(<-ch) }
    c := make(chan string, 1)
    ping(c)
    pong(c)
}

Error Handling

go
package main

import (
    'errors'
    'fmt'
)

// error is a built-in interface: Error() string
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New('cannot divide by zero')
    }
    return a / b, nil
}

// Custom error type
type ValidationError struct {
    Field   string
    Message string
}
func (e *ValidationError) Error() string {
    return fmt.Sprintf('validation error: %s — %s', e.Field, e.Message)
}

// fmt.Errorf with %w — wraps an error
func process(id int) error {
    if id < 0 {
        return fmt.Errorf('process: invalid id %d: %w', id, &ValidationError{
            Field: 'id', Message: 'must be non-negative',
        })
    }
    return nil
}

// errors.Is — checks error chain by value
// errors.As — checks error chain by type
func main() {
    err := process(-1)
    if err != nil {
        fmt.Println(err)

        var ve *ValidationError
        if errors.As(err, &ve) {
            fmt.Printf('field=%s msg=%s\n', ve.Field, ve.Message)
        }
    }

    // Sentinel errors — compare with errors.Is
    var ErrNotFound = errors.New('not found')
    wrapped := fmt.Errorf('lookup: %w', ErrNotFound)
    fmt.Println(errors.Is(wrapped, ErrNotFound))   // true

    // errors.Join (Go 1.20+) — combine multiple errors
    e1 := errors.New('first')
    e2 := errors.New('second')
    combined := errors.Join(e1, e2)
    fmt.Println(combined)
    fmt.Println(errors.Is(combined, e1))   // true

    // Common pattern: check then use
    result, err2 := divide(10, 2)
    if err2 != nil {
        fmt.Println('error:', err2)
        return
    }
    fmt.Println(result)
}

Slices

go
package main

import 'fmt'

func main() {
    // Slice literal
    s := []int{1, 2, 3, 4, 5}

    // make([]T, len, cap) — allocate with length and capacity
    s2 := make([]int, 3)       // len=3, cap=3, all zeros
    s3 := make([]int, 3, 10)   // len=3, cap=10
    _ = s2; _ = s3

    // append — may allocate new backing array
    s = append(s, 6, 7)
    other := []int{8, 9}
    s = append(s, other...)   // spread slice

    // Slicing — shares backing array
    a := s[1:4]   // elements at index 1, 2, 3
    b := s[:3]    // from start
    c := s[3:]    // to end
    d := s[:]     // whole slice
    _ = a; _ = b; _ = c; _ = d

    // 3-index slice — limits capacity of result
    e := s[1:4:5]   // [1:4] with cap limited to 5-1=4
    _ = e

    // copy — copies min(len(dst), len(src)) elements
    dst := make([]int, 3)
    n := copy(dst, s)
    fmt.Println(dst, n)

    // nil slice vs empty slice
    var nilSlice []int          // nil, len=0, cap=0
    emptySlice := []int{}       // not nil, len=0, cap=0
    emptySlice2 := make([]int, 0)
    fmt.Println(nilSlice == nil)   // true
    fmt.Println(emptySlice == nil) // false
    _ = emptySlice2

    // Iterate
    for i, v := range s {
        _ = i; _ = v
    }

    // 2D slice
    matrix := make([][]int, 3)
    for i := range matrix {
        matrix[i] = make([]int, 3)
    }
    matrix[1][1] = 9
    fmt.Println(matrix)

    // Delete element at index i (preserve order)
    i := 2
    s = append(s[:i], s[i+1:]...)

    // Delete (fast, unordered — swaps with last)
    s[i] = s[len(s)-1]
    s = s[:len(s)-1]

    // Insert at index i
    s = append(s[:i+1], s[i:]...)
    s[i] = 99

    fmt.Println(s)
}

Maps

go
package main

import 'fmt'

func main() {
    // Map literal
    m := map[string]int{
        'alice': 90,
        'bob':   85,
    }

    // make(map[K]V) — allocate empty map
    scores := make(map[string]int)

    // Create / Update
    scores['carol'] = 95
    scores['carol'] = 97   // overwrite

    // Read
    v := scores['carol']   // 97
    fmt.Println(v)

    // Existence check — always use two-value form
    val, ok := scores['dave']
    if !ok {
        fmt.Println('dave not found')
    }
    _ = val

    // Delete
    delete(scores, 'carol')

    // Iterate (order is randomized each run)
    for k, v := range m {
        fmt.Printf('%s: %d\n', k, v)
    }

    // Keys only
    for k := range m { _ = k }

    // nil map — reads return zero, writes panic
    var nilMap map[string]int
    fmt.Println(nilMap['x'])   // 0 — safe read
    // nilMap['x'] = 1         // panic: assignment to nil map

    // Map of slices pattern
    groups := make(map[string][]string)
    groups['admin'] = append(groups['admin'], 'alice')
    groups['admin'] = append(groups['admin'], 'bob')
    fmt.Println(groups)

    // Count occurrences
    words := []string{'go', 'is', 'great', 'go', 'is', 'go'}
    freq := make(map[string]int)
    for _, w := range words {
        freq[w]++
    }
    fmt.Println(freq)

    // Set pattern (map to struct{} uses no memory for value)
    seen := make(map[string]struct{})
    seen['go'] = struct{}{}
    _, exists := seen['go']
    fmt.Println(exists)
}

Strings & Runes

go
package main

import (
    'fmt'
    'strings'
    'unicode/utf8'
    'bytes'
)

func main() {
    s := 'Hello, 世界'

    // len returns byte count, not rune count
    fmt.Println(len(s))                      // 13 (UTF-8 bytes)
    fmt.Println(utf8.RuneCountInString(s))   // 9

    // Byte indexing
    fmt.Println(s[0])          // 72 (byte value of 'H')
    fmt.Printf('%c\n', s[0])   // H

    // Rune (codepoint) iteration
    for i, r := range s {
        fmt.Printf('%d: %c (%d bytes)\n', i, r, utf8.RuneLen(r))
    }

    // Convert to rune slice to index by character
    runes := []rune(s)
    fmt.Println(string(runes[7]))   // 世

    // strings package — common operations
    fmt.Println(strings.ToUpper(s))
    fmt.Println(strings.ToLower(s))
    fmt.Println(strings.Contains(s, 'World'))
    fmt.Println(strings.HasPrefix(s, 'Hello'))
    fmt.Println(strings.HasSuffix(s, '界'))
    fmt.Println(strings.Index(s, ','))
    fmt.Println(strings.Count(s, 'l'))
    fmt.Println(strings.Replace(s, 'Hello', 'Hi', 1))
    fmt.Println(strings.ReplaceAll(s, 'l', 'L'))
    fmt.Println(strings.TrimSpace('  hi  '))
    fmt.Println(strings.Trim('--go--', '-'))
    fmt.Println(strings.Split('a,b,c', ','))
    fmt.Println(strings.Join([]string{'x', 'y', 'z'}, '-'))
    fmt.Println(strings.Fields('  foo bar  baz   '))   // split on whitespace

    // strings.Builder — efficient string concatenation
    var sb strings.Builder
    for i := 0; i < 5; i++ {
        fmt.Fprintf(&sb, 'item%d ', i)
    }
    fmt.Println(sb.String())

    // bytes.Buffer — works with []byte
    var buf bytes.Buffer
    buf.WriteString('hello')
    buf.WriteByte(' ')
    buf.WriteString('world')
    fmt.Println(buf.String())

    // String <-> []byte conversion
    b := []byte(s)
    s2 := string(b)
    _ = s2

    // fmt.Sprintf — string formatting
    formatted := fmt.Sprintf('name=%s age=%d pi=%.2f', 'Alice', 30, 3.14159)
    fmt.Println(formatted)
}

Packages & Imports

go
// Package declaration must match directory name
package mathutil

import (
    'fmt'         // standard library
    'math'        // standard library

    'github.com/pkg/errors'   // third-party (go.sum tracks hash)

    myhttp 'myproject/internal/http'   // aliased import
    _      'myproject/side-effects'    // blank import (init() only)
    .      'myproject/dot-import'      // dot import (adds names to scope, avoid)
)

// Exported names start with uppercase
const MaxRetries = 3

// Unexported — package-private
var defaultTimeout = 30

// init() — runs once at program start, after var init
// Multiple init() allowed per file; runs in source order
func init() {
    fmt.Println('mathutil initialized')
    _ = math.Pi
    _ = errors.New
    _ = myhttp.Client{}
    _ = defaultTimeout
}

// Exported function
func Abs(x float64) float64 {
    if x < 0 { return -x }
    return x
}

// Internal package — can only be imported by parent subtree
// e.g. myproject/internal/http can only be imported by myproject/...

Modules

go
# go.mod — module definition file (at repo root)
# Created with: go mod init <module-path>

module github.com/alice/myapp

go 1.21

require (
    github.com/pkg/errors v0.9.1
    golang.org/x/text     v0.14.0
)

require (
    // indirect dependencies
    golang.org/x/sys v0.15.0 // indirect
)

# Common go commands
# go mod init github.com/alice/myapp   — initialize module
# go get github.com/pkg/errors         — add dependency
# go get github.com/pkg/errors@v0.9.1  — pin version
# go get github.com/pkg/errors@latest  — update to latest
# go mod tidy                          — remove unused, add missing
# go mod download                      — download to cache
# go mod vendor                        — copy deps to vendor/
# go list -m all                       — list all dependencies
# go mod graph                         — show dependency graph

# go.sum — cryptographic checksums (commit to VCS)

# Workspace mode (Go 1.18+) — multi-module development
# go work init ./moduleA ./moduleB
# go work use ./moduleC

# go work sync                         — sync workspace deps

# Build constraints / tags
//go:build linux && amd64
// +build linux,amd64   (old syntax, before Go 1.17)

# Cross-compilation
# GOOS=linux GOARCH=amd64 go build -o app-linux .
# GOOS=windows go build -o app.exe .

Generics (Go 1.18+)

go
package main

import (
    'cmp'
    'fmt'
)

// Type parameter with constraint
func Map[T, U any](s []T, f func(T) U) []U {
    result := make([]U, len(s))
    for i, v := range s {
        result[i] = f(v)
    }
    return result
}

func Filter[T any](s []T, f func(T) bool) []T {
    var result []T
    for _, v := range s {
        if f(v) { result = append(result, v) }
    }
    return result
}

func Reduce[T, U any](s []T, init U, f func(U, T) U) U {
    acc := init
    for _, v := range s {
        acc = f(acc, v)
    }
    return acc
}

// comparable — supports == and !=
func Contains[T comparable](s []T, target T) bool {
    for _, v := range s {
        if v == target { return true }
    }
    return false
}

// cmp.Ordered — int, float, string (Go 1.21+)
func Min[T cmp.Ordered](a, b T) T {
    if a < b { return a }
    return b
}

// Custom constraint — union of types
type Number interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 |
    ~float32 | ~float64
}

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

// Generic struct
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, bool) {
    var zero T
    if len(s.items) == 0 { return zero, false }
    v := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return v, true
}

// Multiple type parameters
type Pair[A, B any] struct { First A; Second B }
func NewPair[A, B any](a A, b B) Pair[A, B] { return Pair[A, B]{a, b} }

func main() {
    nums := []int{1, 2, 3, 4, 5}
    doubled := Map(nums, func(n int) int { return n * 2 })
    fmt.Println(doubled)

    evens := Filter(nums, func(n int) bool { return n%2 == 0 })
    fmt.Println(evens)

    total := Reduce(nums, 0, func(acc, n int) int { return acc + n })
    fmt.Println(total)

    fmt.Println(Contains(nums, 3))
    fmt.Println(Min(10, 5))
    fmt.Println(Sum([]float64{1.1, 2.2, 3.3}))

    var st Stack[string]
    st.Push('go')
    st.Push('generics')
    v, ok := st.Pop()
    fmt.Println(v, ok)
}

Testing

go
package calc_test

import (
    'testing'
    'time'
)

// Test function — must start with Test, takes *testing.T
func TestAdd(t *testing.T) {
    got := Add(2, 3)
    want := 5
    if got != want {
        t.Errorf('Add(2,3) = %d; want %d', got, want)
    }
}

// t.Fatal — stops the current test immediately
func TestDivide(t *testing.T) {
    result, err := Divide(10, 2)
    if err != nil {
        t.Fatalf('unexpected error: %v', err)
    }
    if result != 5 {
        t.Errorf('got %v, want 5', result)
    }
}

// Table-driven tests — idiomatic Go
func TestMultiply(t *testing.T) {
    tests := []struct {
        name    string
        a, b    int
        want    int
    }{
        {'positive', 3, 4, 12},
        {'zero',     0, 5, 0},
        {'negative', -2, 3, -6},
    }
    for _, tc := range tests {
        tc := tc   // capture range variable (pre-Go 1.22)
        t.Run(tc.name, func(t *testing.T) {
            t.Parallel()   // run subtests in parallel
            got := Multiply(tc.a, tc.b)
            if got != tc.want {
                t.Errorf('Multiply(%d,%d) = %d; want %d', tc.a, tc.b, got, tc.want)
            }
        })
    }
}

// TestMain — setup/teardown for entire test binary
func TestMain(m *testing.M) {
    // setup
    code := m.Run()
    // teardown
    _ = code
    // os.Exit(code)
}

// Benchmark — must start with Benchmark, takes *testing.B
// Run with: go test -bench=. -benchmem
func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(100, 200)
    }
}

// Benchmark with reset timer
func BenchmarkExpensive(b *testing.B) {
    data := setup()   // expensive setup
    b.ResetTimer()    // exclude setup from timing
    for i := 0; i < b.N; i++ {
        process(data)
    }
}

// Example function — tested by go test, shown in godoc
func ExampleAdd() {
    fmt.Println(Add(1, 2))
    // Output:
    // 3
}

// go test flags:
// go test ./...              — run all tests recursively
// go test -v                 — verbose output
// go test -run TestAdd       — run specific test
// go test -run TestMultiply/zero   — run specific subtest
// go test -count=1           — disable caching
// go test -race              — race detector
// go test -cover             — coverage report
// go test -coverprofile=c.out && go tool cover -html=c.out

func setup() []int  { return []int{1, 2, 3} }
func process([]int) {}
import 'fmt'

Context

go
package main

import (
    'context'
    'fmt'
    'time'
)

func doWork(ctx context.Context) error {
    select {
    case <-time.After(200 * time.Millisecond):
        return nil   // work done
    case <-ctx.Done():
        return ctx.Err()   // context.DeadlineExceeded or context.Canceled
    }
}

func main() {
    // Background — root context, never cancelled
    bg := context.Background()

    // TODO — placeholder, like Background but signals intent
    // ctx := context.TODO()

    // WithCancel — manual cancellation
    ctx, cancel := context.WithCancel(bg)
    defer cancel()   // always defer cancel to avoid leak

    go func() {
        time.Sleep(100 * time.Millisecond)
        cancel()   // cancel the context
    }()

    err := doWork(ctx)
    fmt.Println('WithCancel:', err)   // context canceled

    // WithTimeout — auto-cancel after duration
    ctx2, cancel2 := context.WithTimeout(bg, 50*time.Millisecond)
    defer cancel2()
    err = doWork(ctx2)
    fmt.Println('WithTimeout:', err)   // context deadline exceeded

    // WithDeadline — cancel at specific time
    deadline := time.Now().Add(100 * time.Millisecond)
    ctx3, cancel3 := context.WithDeadline(bg, deadline)
    defer cancel3()
    fmt.Println('deadline:', ctx3.Deadline())

    // WithValue — pass request-scoped values (not for optional params)
    type keyType string
    const userKey keyType = 'userID'

    ctx4 := context.WithValue(bg, userKey, 42)
    if uid, ok := ctx4.Value(userKey).(int); ok {
        fmt.Println('userID:', uid)
    }

    // Propagate context through call chain
    _ = ctx4

    // Check cancellation without blocking
    select {
    case <-ctx.Done():
        fmt.Println('cancelled')
    default:
        fmt.Println('still running')
    }
}

I/O

go
package main

import (
    'bufio'
    'fmt'
    'io'
    'os'
    'path/filepath'
    'strings'
)

func main() {
    // os.Open — read-only
    f, err := os.Open('input.txt')
    if err != nil {
        fmt.Println(err)
        return
    }
    defer f.Close()   // always close

    // Read entire file
    data, err := os.ReadFile('input.txt')
    if err == nil {
        fmt.Println(string(data))
    }

    // Write entire file
    err = os.WriteFile('output.txt', []byte('hello\n'), 0644)
    if err != nil { fmt.Println(err) }

    // Buffered reader — efficient line-by-line reading
    f2, _ := os.Open('input.txt')
    defer f2.Close()
    scanner := bufio.NewScanner(f2)
    for scanner.Scan() {
        line := scanner.Text()
        _ = line
    }
    if err := scanner.Err(); err != nil {
        fmt.Println(err)
    }

    // bufio.Writer — buffered writes
    f3, _ := os.Create('output.txt')
    defer f3.Close()
    w := bufio.NewWriter(f3)
    fmt.Fprintln(w, 'buffered write')
    w.Flush()   // must flush to write remaining buffer

    // io.Reader / io.Writer — core interfaces
    r := strings.NewReader('hello world')
    buf := make([]byte, 4)
    for {
        n, err := r.Read(buf)
        if err == io.EOF { break }
        fmt.Print(string(buf[:n]))
    }
    fmt.Println()

    // io.Copy — zero-alloc stream copy
    src := strings.NewReader('copy me')
    var dst strings.Builder
    io.Copy(&dst, src)
    fmt.Println(dst.String())

    // filepath — cross-platform path operations
    p := filepath.Join('dir', 'sub', 'file.txt')
    fmt.Println(p)
    fmt.Println(filepath.Dir(p))
    fmt.Println(filepath.Base(p))
    fmt.Println(filepath.Ext(p))

    // Walk directory tree
    filepath.WalkDir('.', func(path string, d os.DirEntry, err error) error {
        if err != nil { return err }
        fmt.Println(path, d.IsDir())
        return nil
    })

    // os.Stdin, os.Stdout, os.Stderr
    fmt.Fprintln(os.Stderr, 'error output')
}

Sync Primitives

go
package main

import (
    'fmt'
    'sync'
    'sync/atomic'
)

// Mutex — mutual exclusion for shared state
type SafeCounter struct {
    mu sync.Mutex
    v  map[string]int
}
func (c *SafeCounter) Inc(key string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.v[key]++
}
func (c *SafeCounter) Value(key string) int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.v[key]
}

// RWMutex — multiple concurrent readers, one writer
type SafeMap struct {
    mu sync.RWMutex
    m  map[string]string
}
func (s *SafeMap) Get(k string) string {
    s.mu.RLock()           // shared read lock
    defer s.mu.RUnlock()
    return s.m[k]
}
func (s *SafeMap) Set(k, v string) {
    s.mu.Lock()            // exclusive write lock
    defer s.mu.Unlock()
    s.m[k] = v
}

// sync.Once — run function exactly once
var (
    instance *SafeMap
    once     sync.Once
)
func getInstance() *SafeMap {
    once.Do(func() {
        instance = &SafeMap{m: make(map[string]string)}
    })
    return instance
}

// sync.WaitGroup — wait for goroutines
func waitGroupDemo() {
    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(n int) {
            defer wg.Done()
            fmt.Println(n)
        }(i)
    }
    wg.Wait()
}

// sync/atomic — lock-free operations on primitive types
type AtomicCounter struct {
    count int64
}
func (c *AtomicCounter) Inc()   { atomic.AddInt64(&c.count, 1) }
func (c *AtomicCounter) Get() int64 { return atomic.LoadInt64(&c.count) }
func (c *AtomicCounter) CAS(old, new int64) bool {
    return atomic.CompareAndSwapInt64(&c.count, old, new)
}

// sync.Map — concurrent-safe map (use for high read-to-write ratio)
func syncMapDemo() {
    var m sync.Map
    m.Store('key', 'value')
    v, ok := m.Load('key')
    fmt.Println(v, ok)
    m.Delete('key')
    m.Range(func(k, v interface{}) bool {
        fmt.Println(k, v)
        return true   // return false to stop iteration
    })
}

// sync.Pool — reuse allocated objects to reduce GC pressure
var bufPool = sync.Pool{
    New: func() interface{} { return make([]byte, 1024) },
}
func getBuffer() []byte   { return bufPool.Get().([]byte) }
func putBuffer(b []byte)   { bufPool.Put(b) }

Reflection

go
package main

import (
    'fmt'
    'reflect'
)

type Person struct {
    Name string `json:'name'`
    Age  int    `json:'age'`
}

func main() {
    p := Person{'Alice', 30}

    // reflect.TypeOf — get the type
    t := reflect.TypeOf(p)
    fmt.Println(t.Name())           // Person
    fmt.Println(t.Kind())           // struct
    fmt.Println(t.NumField())       // 2

    // Iterate struct fields
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        fmt.Printf('field=%s type=%s tag=%s\n',
            field.Name, field.Type, field.Tag.Get('json'))
    }

    // reflect.ValueOf — get the value
    v := reflect.ValueOf(p)
    fmt.Println(v.Field(0).String())   // Alice
    fmt.Println(v.Field(1).Int())      // 30

    // Modify via pointer
    pp := &p
    vp := reflect.ValueOf(pp).Elem()   // dereference
    vp.Field(0).SetString('Bob')
    fmt.Println(p.Name)   // Bob

    // Check if a value implements an interface
    var err error
    errType := reflect.TypeOf(&err).Elem()   // reflect.Type of error
    fmt.Println(t.Implements(errType))        // false for Person

    // Create new values dynamically
    newT := reflect.New(t).Elem()
    newT.Field(0).SetString('Carol')
    newT.Field(1).SetInt(25)
    fmt.Println(newT.Interface().(Person))

    // Call methods via reflection
    m := reflect.ValueOf(p).MethodByName('String')
    if m.IsValid() {
        results := m.Call(nil)
        _ = results
    }

    // Slice/map reflection
    nums := []int{1, 2, 3}
    rv := reflect.ValueOf(nums)
    fmt.Println(rv.Kind(), rv.Len())
    for i := 0; i < rv.Len(); i++ {
        fmt.Println(rv.Index(i).Int())
    }

    // reflect.DeepEqual — structural equality
    a := []int{1, 2, 3}
    b := []int{1, 2, 3}
    fmt.Println(reflect.DeepEqual(a, b))   // true
}

CGo

go
package main

/*
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// C function defined inline
int add(int a, int b) {
    return a + b;
}

char* greet(const char* name) {
    char* buf = malloc(64);
    snprintf(buf, 64, "Hello, %s!", name);
    return buf;
}
*/
import 'C'   // must be immediately after the comment block

import (
    'fmt'
    'unsafe'
)

func main() {
    // Call C function
    result := C.add(3, 4)
    fmt.Println(int(result))   // 7

    // Pass Go string to C
    name := C.CString('Go')    // allocates C string — must free!
    defer C.free(unsafe.Pointer(name))

    greeting := C.greet(name)
    defer C.free(unsafe.Pointer(greeting))
    fmt.Println(C.GoString(greeting))   // Hello, Go!

    // C types
    var n C.int = 42
    var f C.double = 3.14
    _ = n; _ = f

    // Convert Go []byte to C pointer
    data := []byte{'h', 'e', 'l', 'l', 'o'}
    ptr := (*C.char)(unsafe.Pointer(&data[0]))
    _ = ptr

    // CGo rules:
    // - Do NOT pass Go pointer to C if it contains Go pointers
    // - C memory must be freed with C.free (not Go GC)
    // - CGo calls have ~200ns overhead vs ~1ns for Go calls
    // - Build: go build (CGo enabled by default)
    // - Disable: CGO_ENABLED=0 go build (pure Go, static binary)
}

// Export Go function to C with //export directive
//
//export GoAdd
func GoAdd(a, b C.int) C.int {
    return a + b
}

Best Practices

Go Idioms

go
// ✅ Accept interfaces, return concrete types
// Maximizes caller flexibility
func NewReader(r io.Reader) *MyReader { ... }   // accept interface
func Open(path string) (*File, error) { ... }   // return concrete

// ✅ Errors are values — handle explicitly, not via exceptions
result, err := doSomething()
if err != nil {
    return fmt.Errorf('doSomething: %w', err)
}

// ✅ Use defer for cleanup — always close what you open
f, err := os.Open(path)
if err != nil { return err }
defer f.Close()

// ✅ Named single-return values only for documentation or naked returns
// Avoid naked returns in long functions — reduces readability

// ✅ Zero value should be useful
type Buffer struct {
    buf []byte   // nil slice is valid, append works on nil
}
var b Buffer     // usable without constructor

// ✅ Prefer composition over inheritance
type Logger struct{ prefix string }
type Server struct {
    Logger
    port int
}

// ✅ Short variable names for small scopes
for i, v := range items { ... }   // i, v are idiomatic
// Longer names for larger scopes
userRepository := NewUserRepository(db)

// ✅ Don't panic in library code — return errors
// panic is for truly unrecoverable programmer errors

// ✅ Use gofmt / goimports — always format code
// go fmt ./...
// goimports -w .

// ✅ Comment exported identifiers with godoc style
// MyFunc does X. It returns Y when Z.
func MyFunc() {}

// ✅ Functional options pattern
type ServerConfig struct {
    port    int
    timeout time.Duration
}
type Option func(*ServerConfig)

func WithPort(p int) Option          { return func(c *ServerConfig) { c.port = p } }
func WithTimeout(d time.Duration) Option { return func(c *ServerConfig) { c.timeout = d } }

func NewServer(opts ...Option) *Server {
    cfg := &ServerConfig{port: 8080, timeout: 30 * time.Second}
    for _, opt := range opts {
        opt(cfg)
    }
    return &Server{config: cfg}
}

Error Handling

go
// ✅ Add context to errors as they propagate
func readConfig(path string) (*Config, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf('readConfig: %w', err)   // wrap with %w
    }
    ...
}

// ✅ Sentinel errors for expected conditions
var (
    ErrNotFound  = errors.New('not found')
    ErrForbidden = errors.New('forbidden')
)

// ✅ Custom error types when callers need to inspect
type NotFoundError struct {
    Resource string
    ID       int
}
func (e *NotFoundError) Error() string {
    return fmt.Sprintf('%s %d not found', e.Resource, e.ID)
}

// ✅ errors.Is / errors.As for matching wrapped errors
if errors.Is(err, ErrNotFound) { ... }

var nfe *NotFoundError
if errors.As(err, &nfe) {
    fmt.Println(nfe.Resource, nfe.ID)
}

// ✅ Return early on error — avoid else after return
func process(id int) (string, error) {
    if id <= 0 {
        return '', ErrNotFound
    }
    // ... no else needed
    return doWork(id)
}

// ✅ Never discard errors silently
// ❌ os.Remove(tmpFile)
// ✅ if err := os.Remove(tmpFile); err != nil { log.Println(err) }

// ✅ panic only for programmer errors (index OOB, nil deref logic)
// Recover from panic in HTTP handlers / goroutine entry points
func safeHandler(h http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if r := recover(); r != nil {
                http.Error(w, 'internal error', 500)
            }
        }()
        h(w, r)
    }
}

Concurrency

go
// ✅ Share memory by communicating, don't communicate by sharing memory
// Prefer channels over mutexes for ownership transfer

// ✅ Document goroutine ownership — who starts, who stops, how it exits
// Every goroutine must have a clear termination path

// ✅ Use context for cancellation — propagate down the call tree
func worker(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            return
        default:
            doWork()
        }
    }
}

// ✅ Use WaitGroup correctly: Add before launching goroutine
var wg sync.WaitGroup
for _, item := range items {
    wg.Add(1)               // ✅ before go
    go func(v Item) {
        defer wg.Done()
        process(v)
    }(item)                 // ✅ pass loop variable as argument
}
wg.Wait()

// ✅ Pipeline pattern — compose goroutines with channels
func gen(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for _, n := range nums { out <- n }
    }()
    return out
}
func sq(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for n := range in { out <- n * n }
    }()
    return out
}

// ✅ Fan-out / fan-in for parallel work
// ✅ Use errgroup for goroutines that return errors
// golang.org/x/sync/errgroup
g, ctx := errgroup.WithContext(context.Background())
for _, url := range urls {
    url := url
    g.Go(func() error { return fetch(ctx, url) })
}
if err := g.Wait(); err != nil { ... }

// ✅ Always run tests with -race flag
// go test -race ./...

Performance

go
// ✅ Profile before optimizing
// go test -cpuprofile=cpu.out -memprofile=mem.out -bench=.
// go tool pprof cpu.out

// ✅ Preallocate slices and maps when size is known
items := make([]Item, 0, expectedCount)   // avoids reallocations
cache := make(map[string]Value, 1000)

// ✅ Use strings.Builder for string concatenation in loops
var sb strings.Builder
sb.Grow(estimatedLen)   // preallocate
for _, s := range strs {
    sb.WriteString(s)
}

// ✅ Reuse allocations with sync.Pool
var pool = sync.Pool{New: func() interface{} { return new(MyStruct) }}
obj := pool.Get().(*MyStruct)
defer pool.Put(obj)

// ✅ Avoid allocations in hot paths
// Value receivers for small structs (copy cheaper than pointer chase)
// Pointer receivers for large structs or when mutation needed

// ✅ Use byte slices instead of strings for I/O
buf := make([]byte, 4096)
n, _ := r.Read(buf)
process(buf[:n])   // no allocation

// ✅ Minimize goroutine creation overhead — use worker pools
type WorkerPool struct {
    jobs chan Job
    wg   sync.WaitGroup
}
func (p *WorkerPool) Start(n int) {
    for i := 0; i < n; i++ {
        p.wg.Add(1)
        go func() {
            defer p.wg.Done()
            for job := range p.jobs { job.Do() }
        }()
    }
}

// ✅ Use GOMAXPROCS = NumCPU (default since Go 1.5)
// ✅ Avoid excessive CGo calls — 200ns overhead per call
// ✅ Use benchmarks to measure: go test -bench=. -benchmem -count=5

Packages & Modules

go
// ✅ Package names: lowercase, single word, no underscores
package httputil   // ✅
package http_util  // ❌
package HTTPUtil   // ❌

// ✅ Package should have one clear purpose
// ❌ package util / helpers / common (too broad)
// ✅ package strconv / filepath / httputil (specific)

// ✅ Use internal/ for implementation details
// myapp/internal/auth    — not importable by external modules
// myapp/internal/db      — forces clean API boundaries

// ✅ Avoid init() side effects in libraries
// ❌ func init() { http.HandleFunc(...) }
// ✅ Require explicit initialization by the caller

// ✅ Version your public APIs carefully
// Use Go module versions for breaking changes:
// module github.com/alice/mylib/v2

// ✅ go mod tidy in CI — catch missing/extra dependencies
// ✅ Pin indirect deps in go.sum — reproducible builds
// ✅ Use go get -u=patch for safe updates (patch versions only)

// ✅ Minimal public API surface — easier to extend than shrink
// Start unexported, export when external need is proven

// ✅ Separate commands from libraries
// cmd/myapp/main.go    — entrypoint (thin, delegates to pkg)
// pkg/server/server.go — library (testable, importable)

// ✅ Use //go:generate for code generation
//go:generate stringer -type=Direction
//go:generate mockgen -source=iface.go -destination=mock.go

Testing

go
// ✅ Table-driven tests are idiomatic Go
func TestAdd(t *testing.T) {
    cases := []struct{ a, b, want int }{
        {1, 2, 3},
        {0, 0, 0},
        {-1, 1, 0},
    }
    for _, tc := range cases {
        if got := Add(tc.a, tc.b); got != tc.want {
            t.Errorf('Add(%d,%d) = %d; want %d', tc.a, tc.b, got, tc.want)
        }
    }
}

// ✅ Use t.Helper() in test utilities to point to the call site
func assertEqual(t *testing.T, got, want int) {
    t.Helper()
    if got != want {
        t.Errorf('got %d, want %d', got, want)
    }
}

// ✅ t.Parallel() for independent tests — faster CI
func TestSomething(t *testing.T) {
    t.Parallel()
    ...
}

// ✅ testdata/ directory — test fixtures, Go tooling ignores it
// testdata/input.json, testdata/golden/expected.txt

// ✅ golden file testing — compare output to checked-in file
// Update with: go test -update-golden ./...

// ✅ Use interfaces + fakes over heavy mocking frameworks
type Store interface { Get(id int) (*User, error) }
type FakeStore struct { users map[int]*User }
func (f *FakeStore) Get(id int) (*User, error) {
    u, ok := f.users[id]
    if !ok { return nil, ErrNotFound }
    return u, nil
}

// ✅ Test package naming
// package calc      — white-box tests (access unexported)
// package calc_test — black-box tests (only exported API)

// ✅ Use -race in CI
// go test -race ./...

// ✅ Keep unit tests fast (< 1s), tag slow tests
//go:build integration
// go test -tags=integration ./...

// ✅ Benchmark to guard performance-sensitive code
func BenchmarkHotPath(b *testing.B) {
    b.ReportAllocs()
    for i := 0; i < b.N; i++ {
        hotPath()
    }
}