Advanced

Example 61: Pipeline Pattern

Pipelines process data through stages, each stage running concurrently. Each stage is a function that receives input from one channel and sends output to another. This composition enables elegant data processing.

  graph TD
    A["Source<br/>generates values"]
    B["Stage 1<br/>transforms"]
    C["Stage 2<br/>filters"]
    D["Sink<br/>collects results"]

    A -->|channel| B
    B -->|channel| C
    C -->|channel| D

    style A fill:#0173B2,stroke:#000,color:#fff
    style B fill:#DE8F05,stroke:#000,color:#fff
    style C fill:#029E73,stroke:#000,color:#fff
    style D fill:#CC78BC,stroke:#000,color:#fff

Code:

package main

import "fmt"

func main() {
    // Create pipeline: generate -> multiply by 2 -> print
    nums := generate(1, 5)         // => Stage 1: generate 1-5
                                    // => nums is <-chan int (receive-only channel)
                                    // => Goroutine starts running in background

    doubled := multiply(nums, 2)   // => Stage 2: multiply by 2
                                    // => doubled is <-chan int (new channel)
                                    // => Second goroutine starts processing nums

    square := multiply(doubled, 2) // => Stage 3: multiply by 2 again (square effect)
                                    // => square is <-chan int (final channel)
                                    // => Third goroutine processes doubled
                                    // => Pipeline: generate(1-5) -> *2 -> *2 = values*4

    // Consume results
    for result := range square {   // => Stage 4: consume results
                                    // => Blocks until value available from square
                                    // => result is 4, then 8, then 12, then 16, then 20
        fmt.Println(result)         // => Output: 4 (first iteration)
                                    // => Output: 8 (second iteration)
                                    // => Output: 12 (third iteration)
                                    // => Output: 16 (fourth iteration)
                                    // => Output: 20 (fifth iteration)
    }
    // => Loop exits when square channel closes
    // => All goroutines complete gracefully
}

// Generator stage - creates values
func generate(start, end int) <-chan int { // => Returns receive-only channel
                                            // => Caller can only receive, not send
    out := make(chan int)                   // => out is bidirectional chan int
                                            // => Unbuffered channel (blocks on send until receive)
    go func() {                             // => Spawn goroutine to generate values
                                            // => Goroutine continues after generate() returns
        for i := start; i <= end; i++ {     // => i is 1, then 2, then 3, then 4, then 5
            out <- i                        // => Send value to channel (blocks until multiply receives)
                                            // => Sends: 1, then 2, then 3, then 4, then 5
        }
        close(out)                          // => Signal completion, no more values
                                            // => Range loops will exit when channel closed
    }()
    return out                              // => Return channel immediately (goroutine runs async)
                                            // => Type converted from chan int to <-chan int
}

// Transform stage - multiplies values
func multiply(in <-chan int, factor int) <-chan int { // => in is receive-only (ensures we don't send)
                                                       // => factor is 2 in all calls
                                                       // => Returns receive-only channel
    out := make(chan int)                              // => Create new output channel
                                                        // => Unbuffered (synchronizes with consumer)
    go func() {                                         // => Spawn goroutine to transform values
                                                        // => Goroutine continues after multiply() returns
        for value := range in {                         // => Range until channel closes
                                                        // => Blocks waiting for input values
                                                        // => value is each input (1,2,3,4,5 or 2,4,6,8,10)
            out <- value * factor                       // => Send transformed value
                                                        // => First multiply: sends 2,4,6,8,10
                                                        // => Second multiply: sends 4,8,12,16,20
        }
        close(out)                                      // => Close output when input exhausted
                                                        // => Signals downstream consumers
    }()
    return out                                          // => Return channel immediately
                                                        // => Type converted from chan int to <-chan int
}

Key Takeaway: Pipelines compose concurrent stages. Each stage receives from one channel, processes, sends to next. Use channels with directional types (<-chan receive, chan<- send) to clarify data flow.

Why It Matters: This concept is fundamental to understanding the language and helps build robust, maintainable code.

Example 62: Context-Aware Pipelines

Pipeline stages should respect cancellation. When context is cancelled, all stages should exit gracefully. This enables cancelling long-running pipelines without leaking goroutines.

  %% Context cancellation propagates through pipeline stages
sequenceDiagram
    participant Main
    participant Ctx as Context
    participant Gen as Generate Stage
    participant Sq as Square Stage
    participant Out as Output

    Main->>Ctx: WithTimeout(100ms)
    Main->>Gen: Start generation
    Main->>Sq: Start squaring
    Gen->>Sq: Send values (1,2,3...)
    Sq->>Out: Send squared (1,4,9...)

    Note over Ctx: 100ms elapsed
    Ctx-->>Gen: ctx.Done() signal
    Ctx-->>Sq: ctx.Done() signal
    Gen->>Gen: Exit gracefully
    Sq->>Sq: Exit gracefully
    Gen->>Sq: Close channel
    Sq->>Out: Close channel
    Out->>Main: Loop exits

    Note over Main,Out: All goroutines cleaned up

Code:

package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    // Create cancellable context
    ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
                                                        // => ctx will cancel after 100ms
                                                        // => cancel is function to cancel early
                                                        // => Background() returns empty root context
    defer cancel()                                      // => Ensure cancel called (releases resources)
                                                        // => Even if context times out, call cancel()

    // Pipeline with context awareness
    nums := generateWithContext(ctx, 1, 100)            // => Generate 1-100 with cancellation
                                                        // => nums is <-chan int
                                                        // => Goroutine respects ctx.Done()

    squared := squareWithContext(ctx, nums)             // => Square each value from nums
                                                        // => squared is <-chan int
                                                        // => Goroutine respects ctx.Done()

    // Consume until context cancelled
    for result := range squared {                       // => Blocks waiting for squared values
                                                        // => result is 1, then 4, then 9, then 16...
        fmt.Println(result)                             // => Output: 1 (first value)
                                                        // => Output: 4 (second value)
                                                        // => Output: 9 (third value)
                                                        // => Outputs continue until context timeout
    }
                                                        // => Loop exits when squared channel closes
                                                        // => Context timeout (100ms) cancels pipeline
    fmt.Println("Pipeline cancelled")                   // => Output: Pipeline cancelled
                                                        // => Printed after all goroutines exit
}

func generateWithContext(ctx context.Context, start, end int) <-chan int {
                                                        // => ctx enables cancellation signal
                                                        // => start=1, end=100
                                                        // => Returns receive-only channel
    out := make(chan int)                               // => Create unbuffered channel
                                                        // => Blocks on send until receiver ready
    go func() {                                         // => Spawn goroutine for generation
                                                        // => Runs concurrently with caller
        defer close(out)                                // => Always close channel when done
                                                        // => Deferred: runs even if return early
        for i := start; i <= end; i++ {                 // => i is 1, then 2, then 3...
                                                        // => Loop continues until i > end or cancelled
            select {                                    // => Multiplexing: send or cancel
            case out <- i:                              // => Send value (blocks until receiver ready)
                                                        // => Sends: 1, 2, 3, 4... until timeout
            case <-ctx.Done():                          // => Context cancelled (timeout or manual)
                                                        // => Receives signal from closed Done() channel
                fmt.Println("Generate cancelled")       // => Output: Generate cancelled
                                                        // => Indicates graceful shutdown
                return                                  // => Exit goroutine early
                                                        // => Deferred close(out) runs
            }
            time.Sleep(10 * time.Millisecond)           // => Simulate work (10ms per value)
                                                        // => 100ms timeout = ~10 values generated
        }
                                                        // => If loop completes naturally, close(out) runs
    }()
    return out                                          // => Return channel immediately
                                                        // => Goroutine continues in background
}

func squareWithContext(ctx context.Context, in <-chan int) <-chan int {
                                                        // => ctx enables cancellation signal
                                                        // => in is input channel from generateWithContext
                                                        // => Returns receive-only channel
    out := make(chan int)                               // => Create unbuffered output channel
                                                        // => Synchronizes with consumer
    go func() {                                         // => Spawn goroutine for transformation
                                                        // => Runs concurrently with generator
        defer close(out)                                // => Always close when done
                                                        // => Signals consumer no more values
        for value := range in {                         // => Range over input channel
                                                        // => Blocks waiting for values from in
                                                        // => value is 1, then 2, then 3...
                                                        // => Exits when in closes
            select {                                    // => Multiplexing: send or cancel
            case out <- value * value:                  // => Send squared value
                                                        // => Sends: 1, 4, 9, 16, 25...
                                                        // => Blocks until consumer receives
            case <-ctx.Done():                          // => Context cancelled
                                                        // => Receives signal from closed Done() channel
                fmt.Println("Square cancelled")         // => Output: Square cancelled
                                                        // => Indicates graceful shutdown
                return                                  // => Exit goroutine early
                                                        // => Deferred close(out) runs
            }
        }
                                                        // => If input exhausted naturally, close(out) runs
    }()
    return out                                          // => Return channel immediately
                                                        // => Goroutine continues in background
}

Key Takeaway: Use select with ctx.Done() in every stage to enable graceful cancellation. When context is cancelled, all stages exit promptly without leaving goroutines running.

Why It Matters: Context-aware cancellation prevents goroutine leaks that plague long-running services, where a cancelled HTTP request must terminate all downstream processing to avoid wasting CPU and memory on orphaned work. Production systems like Prometheus use this pattern to abort expensive metric aggregation queries when clients disconnect, maintaining system stability under load spikes by immediately freeing resources.

Example 63: Rate Limiting

Rate limiting restricts how fast operations occur. Token bucket pattern uses a channel - tokens arrive at a rate, operations consume tokens. When no tokens available, operations wait.

Code:

package main

import (
    "fmt"
    "time"
)

func main() {
    // Create rate limiter - 2 operations per second
    limiter := make(chan struct{}, 2)               // => Channel with capacity 2 (tokens)
                                                    // => Buffered channel holds max 2 empty structs
                                                    // => struct{} uses zero bytes (efficient token)

    // Replenish tokens
    go func() {                                     // => Spawn goroutine to add tokens
                                                    // => Runs concurrently with main
        ticker := time.NewTicker(500 * time.Millisecond)
                                                    // => Add token every 500ms
                                                    // => ticker.C is <-chan time.Time
                                                    // => Sends current time every 500ms
        for range ticker.C {                        // => Receive from ticker every 500ms
                                                    // => Ignores time value (we just need timing)
            select {                                // => Non-blocking send attempt
            case limiter <- struct{}{}:             // => Add token if space available
                                                    // => Sends empty struct to channel
                                                    // => Succeeds if len(limiter) < 2
            default:                                // => Token buffer full, skip
                                                    // => Prevents blocking ticker goroutine
                                                    // => No action needed (already at capacity)
            }
        }
    }()

    // Use limited operations
    for i := 0; i < 5; i++ {                        // => i is 0, then 1, then 2, then 3, then 4
        <-limiter                                   // => Consume token (wait if none available)
                                                    // => Blocks until token available
                                                    // => First 2 operations immediate (initial capacity)
                                                    // => Remaining wait for token replenishment
        fmt.Printf("Operation %d at %v\n", i, time.Now().Unix())
                                                    // => Output: Operation 0 at 1234567890 (immediate)
                                                    // => Output: Operation 1 at 1234567890 (immediate)
                                                    // => Output: Operation 2 at 1234567890 (wait 500ms)
                                                    // => Output: Operation 3 at 1234567891 (wait 500ms)
                                                    // => Output: Operation 4 at 1234567891 (wait 500ms)
    }
}

// Alternative: time.Limit from golang.org/x/time/rate
import "golang.org/x/time/rate"

func limitedOperations() {
    limiter := rate.NewLimiter(rate.Every(time.Second), 5)
                                                    // => 1 op/sec sustained rate
                                                    // => burst capacity of 5 (initial tokens)
                                                    // => rate.Every(time.Second) = 1 operation per second
                                                    // => Allows bursts up to 5 operations

    for i := 0; i < 10; i++ {                       // => i is 0,1,2...9 (10 operations total)
        if !limiter.Allow() {                       // => Check if operation allowed (non-blocking)
                                                    // => Returns true if token available
                                                    // => Consumes token if available
                                                    // => First 5 return true (burst capacity)
                                                    // => Remaining 5 fail (no tokens yet)
            fmt.Println("Rate limit exceeded")      // => Output: Rate limit exceeded (for ops 5-9)
                                                    // => Indicates rejection, not wait
            continue                                // => Skip this operation
        }
        fmt.Printf("Operation %d\n", i)             // => Output: Operation 0 (first 5 operations)
                                                    // => Output: Operation 1
                                                    // => Output: Operation 2
                                                    // => Output: Operation 3
                                                    // => Output: Operation 4
                                                    // => Remaining operations rejected
    }
}

Key Takeaway: Token bucket pattern: channel of limited capacity represents tokens, operations consume tokens. Replenish tokens at fixed rate. This throttles operations smoothly.

Why It Matters: This concept is fundamental to understanding the language and helps build robust, maintainable code.

Example 64: Semaphore Pattern

Semaphores limit concurrent access to resources. While sync.Mutex allows one goroutine at a time, semaphores allow N. Implement with buffered channel of capacity N.

Code:

package main

import (
    "fmt"
    "sync"
)

func main() {
    // Semaphore - allow 3 concurrent operations
    sem := make(chan struct{}, 3)
                                                // => Capacity 3 = 3 concurrent slots
                                                // => Buffered channel as counting semaphore
                                                // => struct{} uses zero memory (empty struct)
    var wg sync.WaitGroup                       // => Tracks completion of all goroutines
                                                // => wg initialized to zero value

    for i := 1; i <= 10; i++ {                  // => Launch 10 goroutines total
                                                // => Only 3 can run simultaneously
        wg.Add(1)                               // => Increment counter before spawning goroutine
                                                // => Critical: Add before go keyword
        go func(id int) {                       // => Goroutine function with id parameter
                                                // => id is copy of i (avoids closure pitfall)
            defer wg.Done()                     // => Decrement counter when goroutine exits
                                                // => Always deferred to ensure execution

            sem <- struct{}{}                   // => Acquire slot (blocks if all 3 full)
                                                // => Only 3 goroutines can acquire simultaneously
                                                // => Send empty struct to channel
            defer func() { <-sem }()            // => Release slot when done
                                                // => Receive from channel frees slot
                                                // => Deferred to ensure release even on panic

            fmt.Printf("Operation %d running\n", id)
                                                // => Output: Operations 1,2,3 first
                                                // => Remaining wait for slot release
                                                // => %d formats id as decimal
            // Simulate work                    // => Real work would go here
        }(i)                                    // => Pass i as argument to goroutine
                                                // => Creates copy, safe for concurrent access
    }

    wg.Wait()                                   // => Block until all 10 goroutines call Done()
                                                // => Counter must reach 0
    fmt.Println("All operations complete")      // => Output: All operations complete
                                                // => Only prints after all goroutines finish
}

// Weighted semaphore - operations require different numbers of slots
func weightedSemaphore() {                      // => Example of weighted semaphore pattern
                                                // => Different operations consume different resources
    sem := make(chan int, 10)                   // => Capacity 10 "units"
                                                // => Simplified weighted example
                                                // => Can hold 10 int values

    // Operation requiring 3 units
    go func() {                                 // => First goroutine (lightweight operation)
        n := 3                                  // => Requires 3 units of resource
                                                // => n is number of units to acquire
        sem <- n                                // => Acquire 3 units (sends int 3)
                                                // => Blocks if channel full
                                                // => Simplified: real weighted needs more logic
        defer func() { <-sem }()                // => Release 3 units (receives int)
                                                // => Frees space in channel

        fmt.Println("Acquired 3 units")         // => Output: Acquired 3 units
                                                // => Work happens here
    }()

    // Operation requiring 7 units
    go func() {                                 // => Second goroutine (heavier operation)
        n := 7                                  // => Requires 7 units of resource
                                                // => Larger operation
        sem <- n                                // => Acquire 7 units (sends int 7)
                                                // => Blocks if insufficient capacity
        defer func() { <-sem }()                // => Release 7 units
                                                // => Returns capacity to pool

        fmt.Println("Acquired 7 units")         // => Output: Acquired 7 units
    }()

    // Total capacity: 10 units, both can run concurrently
                                                // => 3 + 7 = 10 (fits within capacity)
                                                // => Note: True weighted semaphore needs golang.org/x/sync/semaphore
                                                // => This is simplified demonstration
}

Key Takeaway: Semaphore = buffered channel. Capacity = maximum concurrent operations. Send before work, receive after. Useful for limiting concurrent database connections, API calls, or other bounded resources.

Why It Matters: This concept is fundamental to understanding the language and helps build robust, maintainable code.

Example 65: Atomic Operations

Atomic operations ensure thread-safe modifications without mutexes. The sync/atomic package provides compare-and-swap (CAS) and atomic increments. Use when contention is low and operations are simple.

  %% Compare-and-swap atomic operation flow
graph TD
    A["Start: value=5"]
    B["Call CompareAndSwap<br/>expected=5, new=10"]
    C{Current value<br/>equals expected?}
    D["Yes: value==5"]
    E["No: value!=5"]
    F["Atomically swap<br/>value=10"]
    G["Return true"]
    H["No change<br/>value unchanged"]
    I["Return false"]

    A --> B
    B --> C
    C -->|value==5| D
    C -->|value!=5| E
    D --> F
    E --> H
    F --> G
    H --> I

    style A fill:#0173B2,stroke:#000,color:#fff
    style B fill:#DE8F05,stroke:#000,color:#fff
    style C fill:#CC78BC,stroke:#000,color:#fff
    style D fill:#029E73,stroke:#000,color:#fff
    style E fill:#CA9161,stroke:#000,color:#fff
    style F fill:#029E73,stroke:#000,color:#fff
    style G fill:#0173B2,stroke:#000,color:#fff
    style H fill:#CA9161,stroke:#000,color:#fff
    style I fill:#DE8F05,stroke:#000,color:#fff

Code:

package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

func main() {
    // Atomic counter - multiple goroutines increment safely
    var counter int64                               // => Must be int64 or int32 for atomic
                                                    // => Initialized to 0
                                                    // => Must be accessed via atomic package only
    var wg sync.WaitGroup                           // => WaitGroup to synchronize goroutines
                                                    // => Counter starts at 0

    for i := 0; i < 10; i++ {                       // => Launch 10 goroutines
                                                    // => i is 0,1,2...9
        wg.Add(1)                                   // => Increment WaitGroup counter
                                                    // => Counter reaches 10
        go func() {                                 // => Spawn goroutine
                                                    // => Runs concurrently with others
            defer wg.Done()                         // => Decrement WaitGroup when done
                                                    // => Always executes via defer
            for j := 0; j < 100; j++ {              // => Each goroutine increments 100 times
                                                    // => j is 0,1,2...99
                atomic.AddInt64(&counter, 1)        // => Atomic increment (thread-safe)
                                                    // => Adds 1 to counter atomically
                                                    // => No race condition (CPU-level atomic operation)
                                                    // => counter increases: 1,2,3...1000
            }
        }()
    }

    wg.Wait()                                       // => Block until all goroutines complete
                                                    // => Waits for counter to reach 0
    fmt.Println("Counter:", counter)                // => Output: Counter: 1000
                                                    // => Always 1000 (safe from race conditions)
                                                    // => Without atomic: unpredictable (race condition)

    // Atomic swap
    var value int64 = 10                            // => value is 10 (type: int64)
    old := atomic.SwapInt64(&value, 20)             // => Set value to 20, return old (10)
                                                    // => Atomic operation (swap happens atomically)
                                                    // => old is 10, value is now 20
    fmt.Println("Old:", old, "New:", value)         // => Output: Old: 10 New: 20
                                                    // => Demonstrates swap semantics

    // Compare-and-swap (CAS)
    var cas int64 = 5                               // => cas is 5 (type: int64)
    swapped := atomic.CompareAndSwapInt64(&cas, 5, 10)
                                                    // => If cas==5, set to 10 and return true
                                                    // => If cas!=5, no change and return false
                                                    // => cas is 5, so swap succeeds
                                                    // => swapped is true, cas is now 10
    fmt.Println("Swapped:", swapped, "Value:", cas) // => Output: Swapped: true Value: 10
                                                    // => CAS useful for lock-free algorithms

    // Failed CAS example
    swapped2 := atomic.CompareAndSwapInt64(&cas, 5, 15)
                                                    // => cas is 10 (not 5), so swap fails
                                                    // => swapped2 is false, cas unchanged (10)
                                                    // => Demonstrates conditional update

    // Load and store for safe reads
    var flag int32 = 0                              // => flag is 0 (type: int32)
                                                    // => Must use int32 for atomic operations
    atomic.StoreInt32(&flag, 1)                     // => Atomic write (sets flag to 1)
                                                    // => Ensures visibility across goroutines
                                                    // => Memory barrier guarantees ordering
    value32 := atomic.LoadInt32(&flag)              // => Atomic read (reads flag value)
                                                    // => value32 is 1
                                                    // => Ensures we see latest value
    fmt.Println("Flag:", value32)                   // => Output: Flag: 1
                                                    // => Load/Store prevent compiler reordering
}

Key Takeaway: Atomic operations are lock-free. Use atomic.AddInt64() for counters, atomic.SwapInt64() for updates, atomic.CompareAndSwapInt64() for conditional updates. Lower overhead than mutexes but limited to simple operations.

Why It Matters: This concept is fundamental to understanding the language and helps build robust, maintainable code.

Example 66: Reflection

Reflection inspects types and values at runtime. The reflect package enables dynamic code - examine struct fields, call methods, or build values whose type isn’t known until runtime. Use sparingly - reflection is powerful but slow and hard to understand.

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC, Brown #CA9161
graph TD
    A["Value<br/>Person{Name, Age}"]
    B["reflect.ValueOf(p)"]
    C["reflect.Type<br/>main.Person"]
    D["Iterate Fields"]
    E["Field 0: Name<br/>string"]
    F["Field 1: Age<br/>int"]

    A --> B
    B --> C
    B --> D
    D --> E
    D --> F

    style A fill:#0173B2,stroke:#000,color:#fff
    style B fill:#DE8F05,stroke:#000,color:#fff
    style C fill:#029E73,stroke:#000,color:#fff
    style D fill:#CC78BC,stroke:#000,color:#fff
    style E fill:#CA9161,stroke:#000,color:#fff
    style F fill:#CA9161,stroke:#000,color:#fff

Code:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    // Inspect struct type
    type Person struct {                        // => Define struct type
        Name string                             // => First field: Name (type: string)
        Age  int                                // => Second field: Age (type: int)
    }

    p := Person{"Alice", 30}                    // => p is Person{Name:"Alice", Age:30}
                                                // => Struct literal initialization
    v := reflect.ValueOf(p)                     // => Get reflected value wrapping p
                                                // => v is reflect.Value of Person
                                                // => Enables runtime type inspection
    t := v.Type()                               // => Get type information from value
                                                // => t is reflect.Type for main.Person
                                                // => Contains metadata about struct

    fmt.Println("Type:", t)                     // => Output: Type: main.Person
                                                // => Shows package and type name
    fmt.Println("Fields:")                      // => Header for field iteration

    // Iterate struct fields
    for i := 0; i < v.NumField(); i++ {         // => NumField() returns 2 (Name, Age)
                                                // => i is 0, then 1
        field := v.Field(i)                     // => Get field value by index
                                                // => field is reflect.Value for field
                                                // => i=0: field wraps "Alice", i=1: field wraps 30
        fieldType := t.Field(i)                 // => Get field type metadata
                                                // => fieldType is reflect.StructField
                                                // => Contains Name, Type, Tag, Offset
        fmt.Printf("  %s: %v (type: %s)\n", fieldType.Name, field.Interface(), field.Type())
                                                // => fieldType.Name is "Name" or "Age"
                                                // => field.Interface() converts back to interface{}
                                                // => field.Type() is "string" or "int"
    }
    // => Output:
    // =>   Name: Alice (type: string)          // => First field
    // =>   Age: 30 (type: int)                 // => Second field

    // Get value using field name
    nameField := v.FieldByName("Name")          // => Lookup field by string name
                                                // => nameField is reflect.Value wrapping "Alice"
                                                // => Returns zero Value if field not found
    fmt.Println("Name field:", nameField.String())
                                                // => Output: Name field: Alice
                                                // => .String() converts Value to string

    // Check kind
    if t.Kind() == reflect.Struct {             // => Kind() returns underlying type category
                                                // => reflect.Struct is one of 26 kinds
                                                // => Other kinds: Int, String, Slice, etc.
        fmt.Println("Type is a struct")         // => Output: Type is a struct
                                                // => Confirms t represents struct type
    }

    // Type assertion vs reflection
    // Reflection approach (dynamic):
    val := reflect.ValueOf(p)                   // => Create new reflect.Value for p
                                                // => val wraps Person struct
    if val.Kind() == reflect.Struct {           // => Runtime check using reflection
                                                // => Slower than static type check
        fmt.Println("It's a struct (reflection)")
                                                // => Output: It's a struct (reflection)
                                                // => Dynamic approach for unknown types
    }

    // Direct approach (static, faster):
    switch p := p.(type) {                      // => Type switch (compile-time)
                                                // => p shadows outer p variable
    case Person:                                // => Match Person type
                                                // => Compiler knows type at compile-time
        fmt.Println("It's a Person (direct)")   // => Output: It's a Person (direct)
                                                // => Faster than reflection (no runtime overhead)
                                                // => Preferred when types known at compile-time
    }
}

Key Takeaway: Use reflection sparingly - it’s slow and reduces code clarity. Prefer direct type assertions when possible. Reflection is useful for libraries, JSON unmarshaling, or dynamic test fixtures. reflect.ValueOf() gets reflected value, .Type() gets type, .NumField() iterates struct fields.

Why It Matters: Reflection powers critical infrastructure like JSON marshaling (encoding/json), database ORMs (GORM, sqlx), and dependency injection frameworks that need to work with types unknown at compile-time. While slow (10-100x overhead) and fragile (bypasses compile-time type safety), reflection is essential for building generic libraries that inspect struct tags for validation rules or automatically map database columns to struct fields, reducing boilerplate in application code.

Example 67: Binary Encoding

Binary protocols and data formats require reading/writing binary data. The encoding/binary package handles byte order (endianness) and converts between binary and Go types.

Code:

package main

import (
    "bytes"
    "encoding/binary"
    "fmt"
    "io"
)

func main() {
    // Write binary data
    buf := new(bytes.Buffer)                        // => In-memory buffer (implements io.Writer/Reader)
                                                    // => new() allocates and returns pointer

    // Write integer in big-endian format
    binary.Write(buf, binary.BigEndian, int32(42))  // => 42 as 4 bytes: [0x00 0x00 0x00 0x2A]
                                                    // => Big-endian: most significant byte first
                                                    // => int32 is always 4 bytes
    binary.Write(buf, binary.BigEndian, float32(3.14))
                                                    // => Float as 4 bytes (IEEE 754 format)
                                                    // => Writes binary representation of float
    binary.Write(buf, binary.BigEndian, true)       // => Bool as 1 byte (0x01 for true)
                                                    // => buf now has 9 bytes total (4+4+1)
                                                    // => All data serialized to bytes

    // Read back
    reader := bytes.NewReader(buf.Bytes())          // => Create reader from buffer
                                                    // => buf.Bytes() returns underlying byte slice
    var num int32                                   // => Declare variables for reading
                                                    // => Zero value: 0
    var f float32                                   // => Zero value: 0.0
    var b bool                                      // => Zero value: false

    binary.Read(reader, binary.BigEndian, &num)     // => Read 4 bytes → num is 42
                                                    // => Requires pointer to write result
    binary.Read(reader, binary.BigEndian, &f)       // => Read 4 bytes → f is 3.14
                                                    // => Reads from current position
    binary.Read(reader, binary.BigEndian, &b)       // => Read 1 byte → b is true
                                                    // => Reader position advances automatically

    fmt.Printf("Num: %d, Float: %f, Bool: %v\n", num, f, b)
                                                    // => Output: Num: 42, Float: 3.140000, Bool: true
                                                    // => All values deserialized correctly

    // Endianness matters
    smallBuf := new(bytes.Buffer)                   // => New buffer for endianness demo
    binary.Write(smallBuf, binary.LittleEndian, int16(256))
                                                    // => 256 = 0x0100 (binary: 0000000100000000)
                                                    // => Little-endian: [0x00 0x01] (low byte first)
                                                    // => int16 is 2 bytes
    fmt.Printf("Little-endian bytes: %v\n", smallBuf.Bytes())
                                                    // => Output: Little-endian bytes: [0 1]
                                                    // => Byte order: low-to-high

    bigBuf := new(bytes.Buffer)                     // => New buffer for big-endian
    binary.Write(bigBuf, binary.BigEndian, int16(256))
                                                    // => Big-endian: [0x01 0x00] (high byte first)
                                                    // => Byte order: high-to-low
    fmt.Printf("Big-endian bytes: %v\n", bigBuf.Bytes())
                                                    // => Output: Big-endian bytes: [1 0]
                                                    // => Demonstrates endianness difference
                                                    // => Same value, different byte order
}

Key Takeaway: binary.Write() serializes values to binary format. binary.Read() deserializes from binary. Specify endianness (BigEndian or LittleEndian). Endianness is crucial for network protocols and file formats.

Why It Matters: This concept is fundamental to understanding the language and helps build robust, maintainable code.

Example 68: Cryptography Basics

Cryptography is essential for security. Go provides standard cryptographic functions in crypto/* packages. Hash for integrity, random for security, HMAC for authentication, encryption for confidentiality.

Code:

package main

import (
    "crypto/hmac"
    "crypto/rand"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
)

func main() {
    // SHA256 hash - integrity check
    data := "Important message"                 // => Data to hash
                                                // => String converted to bytes for hashing
    hash := sha256.Sum256([]byte(data))         // => Compute SHA-256 hash
                                                // => hash is [32]byte (256 bits)
                                                // => Deterministic: same input = same hash
    fmt.Printf("SHA256: %s\n", hex.EncodeToString(hash[:]))
                                                // => Output: 64 hex characters (32 bytes * 2)
                                                // => hash[:] converts array to slice

    // HMAC - authentication
    key := []byte("secret-key")                 // => Shared secret key (known to both parties)
                                                // => In production, use strong random key
    h := hmac.New(sha256.New, key)              // => Create HMAC-SHA256 hasher
                                                // => sha256.New is hash function
    h.Write([]byte(data))                       // => Add data to hash
                                                // => Can call Write multiple times
    signature := hex.EncodeToString(h.Sum(nil)) // => Get signature as hex (64 chars)
                                                // => nil means append to nothing
    fmt.Println("HMAC:", signature)             // => Unique for this data+key combination
                                                // => Output: HMAC: <64 hex chars>

    // Verify HMAC
    h2 := hmac.New(sha256.New, key)             // => New hasher with same key
                                                // => Must use same hash function
    h2.Write([]byte(data))                      // => Hash same data
                                                // => Produces same signature if data unchanged
    if hmac.Equal(h.Sum(nil), h2.Sum(nil)) {    // => Constant-time comparison (prevents timing attacks)
                                                // => Never use == for crypto comparison
        fmt.Println("HMAC valid")               // => Data not tampered
                                                // => Output: HMAC valid
    }

    // Random bytes - for tokens, nonces
    token := make([]byte, 16)                   // => 16-byte slice (128 bits)
                                                // => Allocates space for random data
    _, err := rand.Read(token)                  // => Cryptographically secure random
                                                // => Fills token with random data
                                                // => Uses /dev/urandom on Unix
    if err != nil {                             // => Check for entropy source failure
        fmt.Println("Error generating random:", err)
        return                                  // => Abort if random generation fails
    }
    fmt.Printf("Random token: %s\n", hex.EncodeToString(token))
                                                // => 32 hex chars (16 bytes * 2)
                                                // => Different every time (unpredictable)
                                                // => Output: Random token: <random hex>
}

Key Takeaway: Use crypto/sha256.Sum256() for hashing. Use crypto/hmac with hash function for authentication. Use crypto/rand.Read() for cryptographically secure random bytes. Never use math/rand for security-sensitive operations.

Why It Matters: This concept is fundamental to understanding the language and helps build robust, maintainable code.

Example 69: Templates

Templates generate text (HTML, email, config files). The text/template package provides template syntax with variables, functions, and control flow. Use html/template for HTML to prevent injection attacks.

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC, Brown #CA9161
graph TD
    A["Template String<br/>Hello {{.Name}}"]
    B["template.Parse()"]
    C["Compiled Template"]
    D["Data<br/>{Name: Alice}"]
    E["template.Execute()"]
    F["Output<br/>Hello Alice"]

    A --> B
    B --> C
    C --> E
    D --> E
    E --> F

    style A fill:#0173B2,stroke:#000,color:#fff
    style B fill:#DE8F05,stroke:#000,color:#fff
    style C fill:#029E73,stroke:#000,color:#fff
    style D fill:#CC78BC,stroke:#000,color:#fff
    style E fill:#CA9161,stroke:#000,color:#fff
    style F fill:#0173B2,stroke:#000,color:#fff

Code:

package main

import (
    "fmt"
    "html/template"
    "os"
    "strings"
)

func main() {
    // Simple template with variables
    tmpl, err := template.New("test").Parse("Hello, {{.Name}}! You are {{.Age}} years old.")
                                                    // => Create new template named "test"
                                                    // => Parse template string with {{}} placeholders
                                                    // => {{.Name}} accesses Name field in data
                                                    // => {{.Age}} accesses Age field
                                                    // => Returns (*Template, error)
    if err != nil {                                 // => Check parse error
                                                    // => Syntax errors caught here
        fmt.Println("Parse error:", err)            // => Output: Parse error: <error>
        return                                      // => Exit on error
    }

    data := map[string]interface{}{                 // => Data to render template
        "Name": "Alice",                            // => Name field is "Alice"
        "Age":  30,                                 // => Age field is 30 (int)
    }
    tmpl.Execute(os.Stdout, data)                   // => Execute template with data
                                                    // => Writes to os.Stdout (stdout)
                                                    // => {{.Name}} replaced with "Alice"
                                                    // => {{.Age}} replaced with 30
    // => Output: Hello, Alice! You are 30 years old.
                                                    // => Template rendered to output

    // Conditional and loops
    tmpl2, _ := template.New("list").Parse(`
Users:
{{range .Users}}
  - {{.Name}} ({{.Age}})
{{end}}
`)                                                  // => Multiline template string
                                                    // => {{range .Users}} iterates slice
                                                    // => Inside range, . is each element
                                                    // => {{end}} closes range block
                                                    // => _ ignores error (for brevity)

    data2 := map[string]interface{}{                // => Data with Users slice
        "Users": []map[string]interface{}{          // => Slice of user maps
            {"Name": "Alice", "Age": 30},           // => First user
            {"Name": "Bob", "Age": 25},             // => Second user
        },
    }
    tmpl2.Execute(os.Stdout, data2)                 // => Execute template
                                                    // => {{range}} iterates Users slice
                                                    // => First iteration: . is {"Name": "Alice", "Age": 30}
                                                    // => Second iteration: . is {"Name": "Bob", "Age": 25}
    // => Output:
    // => Users:
    // =>   - Alice (30)                            // => First user rendered
    // =>   - Bob (25)                              // => Second user rendered

    // Custom functions
    funcMap := template.FuncMap{                    // => Map of custom template functions
                                                    // => FuncMap is map[string]interface{}
        "upper": strings.ToUpper,                   // => Add function to template
                                                    // => upper calls strings.ToUpper
        "add":   func(a, b int) int { return a + b }, // => Custom function
                                                    // => add accepts 2 ints, returns sum
    }

    tmpl3, _ := template.New("funcs").Funcs(funcMap).Parse(
        "{{upper .Name}} total is {{add .Age .Years}}",
    )                                               // => Create template with custom functions
                                                    // => Funcs() adds functions before Parse()
                                                    // => {{upper .Name}} calls upper function
                                                    // => {{add .Age .Years}} calls add function

    data3 := map[string]interface{}{                // => Data for function template
        "Name":  "alice",                           // => Name is lowercase "alice"
        "Age":   30,                                // => Age is 30
        "Years": 5,                                 // => Years is 5
    }
    tmpl3.Execute(os.Stdout, data3)                 // => Execute template
                                                    // => {{upper .Name}} becomes "ALICE"
                                                    // => {{add .Age .Years}} becomes 35 (30+5)
    // => Output: ALICE total is 35                 // => Custom functions applied
}

Key Takeaway: Use template.Parse() to create templates. Use .Field to access data. Use {{range}} for loops, {{if}} for conditions. Use html/template instead of text/template for HTML to prevent injection. Define custom functions with FuncMap.

Why It Matters: Templates generate dynamic HTML, emails, and configuration files while preventing injection attacks through automatic escaping in html/template. Production systems use templates for rendering web pages (Hugo static site generator), generating Kubernetes manifests from values, and composing email notifications with user data, where text/template’s lack of escaping would allow XSS attacks if user input reaches the output.

Example 70: Generic Functions

Generics enable functions to work with different types while maintaining type safety. Type parameters in square brackets define constraints. Go 1.18+ introduces this powerful feature.

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC, Brown #CA9161
graph TD
    A["Generic Function<br/>max[T Ordered]"]
    B["Called with []int"]
    C["Called with []string"]
    D["Called with []float64"]
    E["Compiler generates<br/>max_int version"]
    F["Compiler generates<br/>max_string version"]
    G["Compiler generates<br/>max_float64 version"]

    A --> B
    A --> C
    A --> D
    B --> E
    C --> F
    D --> G

    style A fill:#0173B2,stroke:#000,color:#fff
    style B fill:#DE8F05,stroke:#000,color:#fff
    style C fill:#DE8F05,stroke:#000,color:#fff
    style D fill:#DE8F05,stroke:#000,color:#fff
    style E fill:#029E73,stroke:#000,color:#fff
    style F fill:#029E73,stroke:#000,color:#fff
    style G fill:#029E73,stroke:#000,color:#fff

Code:

package main

import "fmt"

func main() {
    // Generic function works with different types
    intSlice := []int{3, 1, 4, 1, 5}            // => intSlice is []int with 5 elements
                                                // => Contains: [3, 1, 4, 1, 5]
    fmt.Println("Max int:", max(intSlice))      // => Calls max with T=int
                                                // => Output: Max int: 5
                                                // => Compiler generates max_int version

    stringSlice := []string{"apple", "zebra", "banana"}
                                                // => stringSlice is []string with 3 elements
                                                // => Contains: ["apple", "zebra", "banana"]
    fmt.Println("Max string:", max(stringSlice))// => Calls max with T=string
                                                // => Output: Max string: zebra
                                                // => Compiler generates max_string version
}

// Generic function - [T any] is type parameter
// T is constrained to "any" type
func max[T any](slice []T) T {                  // => Type parameter T can be any type
                                                // => any is alias for interface{}
                                                // => slice is []T (slice of type T)
    // Compiler error: can't compare T values (no constraint)
    // This won't work because we need to define T must be comparable
    return slice[0]                             // => Returns first element (placeholder)
                                                // => Real implementation needs constraints
}

// Better: constrain T to be comparable
import "fmt"

// Comparable constraint - enables comparison operators
func betterMax[T interface{ int | float64 | string }](slice []T) T {
                                                // => T constrained to int OR float64 OR string
                                                // => Union type constraint
                                                // => Enables > operator for these types
    if len(slice) == 0 {                        // => Check for empty slice
                                                // => len(slice) returns 0 for empty
                                                // => Prevents index out of range panic
        var zero T                              // => zero is zero value for type T
                                                // => T=int: zero=0, T=string: zero=""
        return zero                             // => Return zero value
    }

    max := slice[0]                             // => max starts with first element
                                                // => max is type T
    for _, val := range slice[1:] {             // => Iterate from index 1 onwards
                                                // => slice[1:] creates subslice
                                                // => val is each element (type T)
        if val > max {                          // => Compare values (works with constraint)
                                                // => > operator enabled by union constraint
            max = val                           // => Update max if val is greater
        }
    }
    return max                                  // => Return maximum value (type T)
}

// Even better: use Ordered constraint (Go 1.21+)
import "golang.org/x/exp/constraints"

func bestMax[T constraints.Ordered](slice []T) T {
                                                // => T constrained to Ordered types
                                                // => Ordered includes: integers, floats, strings
                                                // => Standard constraint from constraints package
    if len(slice) == 0 {                        // => Handle empty slice
        var zero T                              // => zero value for type T
        return zero                             // => Return zero value
    }

    max := slice[0]                             // => Initialize max with first element
                                                // => max is type T
    for _, val := range slice[1:] {             // => Iterate remaining elements
                                                // => val is each element after first
        if val > max {                          // => Compare using > operator
                                                // => Ordered constraint enables comparisons
            max = val                           // => Update max if needed
        }
    }
    return max                                  // => Return maximum value found
                                                // => Type is T (preserves input type)
}

Key Takeaway: Generic functions use [T TypeConstraint] syntax. Type parameter T is replaced with actual type at compile-time. Constraints limit what operations T supports. any means no constraints (but limited what you can do).

Why It Matters: Generics (Go 1.18+) eliminate code duplication for data structures and algorithms, replacing brittle interface{}+reflection patterns with compile-time type safety. Generic max() functions work across int/float/string without type assertions, generic min-heaps work with any comparable type, and generic Result<T,E> types enable Rust-style error handling, all providing zero runtime overhead through monomorphization (compiler generates specialized versions per type).

Example 71: Generic Types

Generic struct types work similarly to generic functions. Define type parameters, and the compiler instantiates them for each type used. Useful for containers, queues, trees, and data structures.

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC, Brown #CA9161
graph TD
    A["Generic Type<br/>Stack[T any]"]
    B["Stack[int]<br/>items []int"]
    C["Stack[string]<br/>items []string"]
    D["Stack[Person]<br/>items []Person"]

    A -->|instantiate| B
    A -->|instantiate| C
    A -->|instantiate| D

    B --> E["Push(10)<br/>Pop() int"]
    C --> F["Push(\"hi\")<br/>Pop() string"]
    D --> G["Push(person)<br/>Pop() Person"]

    style A fill:#0173B2,stroke:#000,color:#fff
    style B fill:#DE8F05,stroke:#000,color:#fff
    style C fill:#DE8F05,stroke:#000,color:#fff
    style D fill:#DE8F05,stroke:#000,color:#fff
    style E fill:#029E73,stroke:#000,color:#fff
    style F fill:#029E73,stroke:#000,color:#fff
    style G fill:#029E73,stroke:#000,color:#fff

Code:

package main

import "fmt"

func main() {
    // Generic stack of integers
    intStack := NewStack[int]()                     // => Create Stack[int] instance
                                                    // => T=int, instantiates Stack with int
                                                    // => intStack is *Stack[int]
    intStack.Push(10)                               // => Push 10 (type: int)
                                                    // => items is now []int{10}
    intStack.Push(20)                               // => Push 20 (type: int)
                                                    // => items is now []int{10, 20}
    fmt.Println("Pop:", intStack.Pop())             // => Pop returns 20 (last item)
                                                    // => Output: Pop: 20
                                                    // => items is now []int{10}

    // Generic stack of strings
    stringStack := NewStack[string]()               // => Create Stack[string] instance
                                                    // => T=string, instantiates Stack with string
                                                    // => stringStack is *Stack[string]
    stringStack.Push("hello")                       // => Push "hello" (type: string)
                                                    // => items is now []string{"hello"}
    stringStack.Push("world")                       // => Push "world" (type: string)
                                                    // => items is now []string{"hello", "world"}
    fmt.Println("Pop:", stringStack.Pop())          // => Pop returns "world" (last item)
                                                    // => Output: Pop: world
                                                    // => items is now []string{"hello"}
}

// Generic stack type
type Stack[T any] struct {                          // => Type parameter T (can be any type)
                                                    // => Stack is generic over T
                                                    // => Compiler generates type-specific versions
    items []T                                       // => Slice of type T (not interface{})
                                                    // => T=int: items is []int
                                                    // => T=string: items is []string
}

// Generic methods
func (s *Stack[T]) Push(item T) {                   // => Method on Stack[T]
                                                    // => item must be type T
                                                    // => Receiver is *Stack[T] (pointer)
    s.items = append(s.items, item)                 // => Append item to slice
                                                    // => Type-safe: item is T, items is []T
                                                    // => Grows slice if needed
}

func (s *Stack[T]) Pop() T {                        // => Method returns type T
                                                    // => Return type matches Stack type parameter
    if len(s.items) == 0 {                          // => Check for empty stack
                                                    // => Prevents out-of-bounds access
        var zero T                                  // => zero is zero value for type T
                                                    // => T=int: zero=0, T=string: zero=""
        return zero                                 // => Return zero value (safe default)
    }
    lastIdx := len(s.items) - 1                     // => Index of last element
                                                    // => len(s.items)-1 is last valid index
    item := s.items[lastIdx]                        // => Get last item
                                                    // => item is type T
    s.items = s.items[:lastIdx]                     // => Remove last item (slice truncation)
                                                    // => Creates new slice view
                                                    // => Original item unreachable (GC'd)
    return item                                     // => Return popped item (type T)
}

// Constructor
func NewStack[T any]() *Stack[T] {                  // => Generic constructor function
                                                    // => T is type parameter
                                                    // => Returns pointer to Stack[T]
    return &Stack[T]{                               // => Create Stack instance
                                                    // => Stack[T] instantiated with type T
        items: make([]T, 0),                        // => Initialize empty slice of type T
                                                    // => Capacity 0 (grows as needed)
    }
}

// Generic interface
type Container[T any] interface {                   // => Generic interface over type T
                                                    // => Types implementing must specify T
    Add(T)                                          // => Method accepting type T
                                                    // => T must match interface instantiation
    Remove() T                                      // => Method returning type T
                                                    // => Type-safe: always same T
}
                                                    // => Stack[T] satisfies Container[T]
                                                    // => Stack[int] implements Container[int]
                                                    // => Stack[string] implements Container[string]

Key Takeaway: Define generic types with Type[T TypeParam]. Methods on generic types use the type parameter. Construct with NewGeneric[Type](). Generic types enable type-safe reusable containers.

Why It Matters: Generic containers like Stack[T], Queue[T], and Cache[K,V] provide type-safe reusable data structures without runtime type assertions or interface{} boxing overhead. Where pre-generics Go required separate IntStack/StringStack implementations or unsafe interface{} casts, generic types enable writing containers once and using everywhere with full compile-time type checking, eliminating entire classes of runtime type errors.

Example 72: Constraints and Comparable

Go provides built-in type constraints. The comparable constraint enables == and != operators. Custom constraints combine types and interfaces using type unions.

Code:

package main

import "fmt"

func main() {
    // Integer constraint - int types only
    fmt.Println("Sum ints:", sum([]int{1, 2, 3}))         // => Calls sum with []int
                                                           // => T inferred as int
                                                           // => Output: Sum ints: 6
    fmt.Println("Sum int64:", sum([]int64{1, 2, 3}))     // => Calls sum with []int64
                                                           // => T inferred as int64
                                                           // => Output: Sum int64: 6

    // Comparable constraint - can use == and !=
    if contains([]string{"a", "b", "c"}, "b") {   // => Calls contains with string slice
                                                    // => T inferred as string
                                                    // => Returns true ("b" found)
        fmt.Println("Found")                       // => Output: Found
    }

    // Custom constraint - combine types and interface
    var m map[string]int = make(map[string]int)   // => Create map with string keys
                                                    // => m is map[string]int
    m["key"] = 10                                  // => Set value
                                                    // => m is map[string:10]
    fmt.Println("Value:", getValue(m, "key"))     // => Calls getValue with string key
                                                    // => K inferred as string, V as int
                                                    // => Output: Value: 10
}

// Integer constraint using type union
type Integer interface {                          // => Custom constraint for integer types
    ~int | ~int8 | ~int16 | ~int32 | ~int64 |    // => ~ means "underlying type"
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 // => Accepts all integer types
}
                                                   // => Type union defines allowed types

func sum[T Integer](nums []T) T {                // => Generic function with Integer constraint
                                                   // => T must be integer type (int, int64, etc.)
                                                   // => nums is slice of T
    var total T                                    // => total is zero value of T (0)
    for _, n := range nums {                       // => n is each element
                                                    // => First: n=1, Second: n=2, Third: n=3
        total += n                                 // => Add to total
                                                    // => total becomes 1, then 3, then 6
    }
    return total                                   // => Returns final sum (6)
}

// Comparable constraint - can use == and !=
func contains[T comparable](slice []T, target T) bool { // => Generic function
                                                         // => T must support == and !=
                                                         // => comparable is built-in constraint
    for _, item := range slice {                  // => item is each element
                                                    // => First: "a", Second: "b", Third: "c"
        if item == target {                       // => Compare using ==
                                                    // => Works because T is comparable
                                                    // => "b" == "b" is true
            return true                            // => Found match
        }
    }
    return false                                   // => No match found
}

// Custom constraint combining comparable
type MapKey interface {                           // => Custom constraint interface
    comparable                                    // => Embeds comparable constraint
                                                   // => Types must support == and !=
}
                                                   // => MapKey = comparable (same as built-in)

func getValue[K MapKey, V any](m map[K]V, key K) V { // => Two type parameters
                                                       // => K must satisfy MapKey (comparable)
                                                       // => V can be any type
                                                       // => m is map with K keys and V values
    return m[key]                                  // => Access map by key
                                                    // => Returns value of type V
}

Key Takeaway: Define custom constraints using type unions (~int | ~int8 | ...). comparable = types supporting == and != (built-in). ~type means “underlying type matches”. Constraints enable operator usage in generic functions.

Why It Matters: Type constraints enable generic functions to use operators (comparable for ==, custom Integer for +/-/*) while maintaining type safety, solving the pre-generics problem where generic code couldn’t perform comparisons without reflection. The comparable constraint powers generic contains() functions and Set[T] implementations, while custom type union constraints enable generic sum/min/max functions, providing operator support that interface{} could never offer.

Example 73: Options Pattern

The options pattern provides flexible configuration through functional options. Each option function modifies configuration without requiring many constructors or mutating shared state.

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC, Brown #CA9161
graph TD
    A["NewServer#40;#41;"]
    B["Default Config<br/>Host: 0.0.0.0<br/>Port: 80"]
    C["WithHost#40;localhost#41;"]
    D["WithPort#40;8080#41;"]
    E["WithTimeout#40;30#41;"]
    F["Final Config<br/>Host: localhost<br/>Port: 8080<br/>Timeout: 30"]

    A --> B
    B --> C
    C --> D
    D --> E
    E --> F

    style A fill:#0173B2,stroke:#000,color:#fff
    style B fill:#DE8F05,stroke:#000,color:#fff
    style C fill:#029E73,stroke:#000,color:#fff
    style D fill:#029E73,stroke:#000,color:#fff
    style E fill:#029E73,stroke:#000,color:#fff
    style F fill:#CC78BC,stroke:#000,color:#fff

Code:

package main

import "fmt"

func main() {
    // Simple config without options
    server1 := NewServer()                    // => Creates server with defaults
                                               // => server1.Host is "0.0.0.0"
                                               // => server1.Port is 80
                                               // => server1.Timeout is 10
    fmt.Println(server1)                      // => Output: Server{Host: 0.0.0.0, Port: 80, Timeout: 10}

    // Config with options
    server2 := NewServer(                     // => Starts with defaults
        WithHost("localhost"),                // => Functional option modifies Host
                                               // => server2.Host becomes "localhost"
        WithPort(8080),                       // => Functional option modifies Port
                                               // => server2.Port becomes 8080
        WithTimeout(30),                      // => Functional option modifies Timeout
                                               // => server2.Timeout becomes 30
    )
                                               // => Final: Host=localhost, Port=8080, Timeout=30
    fmt.Println(server2)                      // => Output: Server{Host: localhost, Port: 8080, Timeout: 30}

    // Mix options
    server3 := NewServer(                     // => Partial options usage
        WithPort(9000),                       // => Only Port modified
                                               // => server3.Port becomes 9000
        // WithHost uses default
                                               // => server3.Host stays "0.0.0.0" (default)
        WithTimeout(60),                      // => Timeout modified
                                               // => server3.Timeout becomes 60
    )
                                               // => Final: Host=0.0.0.0 (default), Port=9000, Timeout=60
    fmt.Println(server3)                      // => Output: Server{Host: 0.0.0.0, Port: 9000, Timeout: 60}
}

type Server struct {                          // => Configuration struct
    Host    string                            // => Server hostname/IP
    Port    int                               // => Server port number
    Timeout int                               // => Connection timeout in seconds
}

// Functional option type
type Option func(*Server)                     // => Option is function that modifies Server
                                               // => Takes *Server pointer to modify in-place
                                               // => Returns nothing (side effects only)

// Constructor
func NewServer(opts ...Option) *Server {     // => Variadic options parameter
                                               // => opts can be 0 or more Option functions
    s := &Server{                             // => Create Server with defaults
        Host:    "0.0.0.0",                   // => Default: listen on all interfaces
        Port:    80,                          // => Default: standard HTTP port
        Timeout: 10,                          // => Default: 10 second timeout
    }
                                               // => s is &Server{0.0.0.0, 80, 10}

    for _, opt := range opts {                // => Iterate over option functions
                                               // => opt is each Option function
        opt(s)                                // => Call option function with server pointer
                                               // => Modifies s in-place
    }
                                               // => After all options applied, s has final config

    return s                                  // => Return configured server
}

// Option functions
func WithHost(host string) Option {          // => Returns Option function (closure)
                                               // => host is captured by closure
    return func(s *Server) {                 // => Returned function modifies Server
        s.Host = host                        // => Set Host field to captured host value
                                               // => Modifies server in-place
    }
}
                                               // => Returns closure that sets Host field

func WithPort(port int) Option {             // => Returns Option function
                                               // => port is captured by closure
    return func(s *Server) {                 // => Returned function modifies Server
        s.Port = port                        // => Set Port field to captured port value
    }
}
                                               // => Returns closure that sets Port field

func WithTimeout(timeout int) Option {       // => Returns Option function
                                               // => timeout is captured by closure
    return func(s *Server) {                 // => Returned function modifies Server
        s.Timeout = timeout                  // => Set Timeout field to captured timeout value
    }
}
                                               // => Returns closure that sets Timeout field

func (s Server) String() string {            // => Stringer interface implementation
                                               // => s is value receiver (copy)
    return fmt.Sprintf("Server{Host: %s, Port: %d, Timeout: %d}", s.Host, s.Port, s.Timeout)
                                               // => Returns formatted string representation
}

Key Takeaway: Options pattern accepts variadic functions that modify a config struct. Each option is a function that receives and modifies the struct. Enables flexible API without many constructors or mutating state.

Why It Matters: This concept is fundamental to understanding the language and helps build robust, maintainable code.

Example 74: Embed Directive (Go 1.16+)

The //go:embed directive embeds files into the binary at compile-time. Useful for static assets, templates, or configuration files that should be part of the executable.

Code:

package main

import (
    "embed"                                  // => Package for embedded file systems
    "fmt"
)

func main() {
    // Embedded file
    fmt.Println("HTML template:")            // => Output: HTML template:
    fmt.Println(string(htmlContent))         // => Content embedded at compile-time
                                              // => htmlContent is []byte containing index.html
                                              // => Converts to string and prints content
                                              // => No file I/O at runtime

    // Embedded file system
    entries, _ := templates.ReadDir("templates") // => Read directory from embedded FS
                                              // => entries is []fs.DirEntry
                                              // => Contains all files in templates/ directory
    fmt.Println("Embedded files:", len(entries)) // => Output: Embedded files: 2
                                              // => Number of files in templates/ directory
    for _, entry := range entries {          // => entry is each DirEntry
        fmt.Println("  -", entry.Name())     // => Output: - index.html
                                              // => Output: - style.css
    }

    // Read specific file from embedded FS
    content, _ := templates.ReadFile("templates/index.html") // => Read specific file
                                              // => content is []byte
                                              // => File read from embedded FS (no disk access)
    fmt.Println("File content:", string(content)) // => Output: File content: [HTML content]
                                              // => Converts to string and prints
}

// Single file
//go:embed templates/index.html              // => Compiler directive
var htmlContent []byte                       // => Content embedded at compile-time
                                              // => htmlContent contains file bytes
                                              // => File must exist at compile-time
                                              // => No runtime file access needed

// File system
//go:embed templates/*                       // => Embed entire directory
var templates embed.FS                       // => Entire directory embedded
                                              // => templates is embedded file system
                                              // => Can use fs.FS interface methods
                                              // => All files in templates/ included

// String content
//go:embed config.json                       // => Embed as string instead of []byte
var config string                            // => config contains JSON as string
                                              // => Alternative to []byte for text files

Key Takeaway: //go:embed path embeds files into the binary. Single file type is []byte or string. Directory type is embed.FS. Files are embedded at compile-time, no runtime file system access needed.

Why It Matters: Embedding files into binaries eliminates deployment dependencies and version skew, enabling single-binary deployment where static assets (HTML templates, SQL migrations, configuration defaults) ship inside the executable. This powers Hugo’s single-binary distribution with 300+ embedded templates, eliminates “template file not found” runtime errors, and enables hermetic builds where embedded files cannot be tampered with post-compilation, critical for security-sensitive applications.

Example 75: Build Tags

Build tags enable conditional compilation. Platform-specific code, feature flags, or test-only code can be controlled with build tags. Tag expressions determine which files compile.

Code:

// file: server_unix.go
//go:build unix || linux           // => Compile only on Unix/Linux (OR matches any Unix-like)
                                    // => Modern format (Go 1.17+)
// +build unix linux                // => Legacy format (pre-Go 1.17) for compatibility
                                    // => Both lines needed for backward compatibility

package main

import "fmt"

func getPlatform() string {         // => Unix/Linux implementation
                                    // => Only compiled on Unix/Linux systems
    return "Unix/Linux"
}

// file: server_windows.go
//+build windows                    // => Compile only on Windows
                                    // => Compiler skips this file on non-Windows

package main

import "fmt"

func getPlatform() string {         // => Windows implementation
                                    // => Different implementation, same function name
    return "Windows"
}

// file: main.go
package main                        // => No build tags: compiles on ALL platforms

func main() {
    fmt.Println("Platform:", getPlatform())
                                    // => Linux: "Platform: Unix/Linux"
                                    // => Windows: "Platform: Windows"
                                    // => Linker selects correct implementation per OS
                                    // => No runtime conditionals needed
}

// Usage: go build                  // => Auto-selects OS-specific files (via GOOS/GOARCH)
                                    // => GOOS and GOARCH set by environment
// Usage: go build -tags=feature1   // => Enables custom 'feature1' tag
                                    // => Custom tags for feature flags
// Usage: go run -tags="tag1,tag2"  // => Multiple tags (all must be satisfied)

// Multiple tags in single file
//go:build (linux || darwin) && !debug  // => (Linux OR macOS) AND NOT debug
                                          // => Boolean expressions with ()
// +build linux darwin               // => Legacy: space-separated = OR
// +build !debug                     // => Legacy: multiple lines = AND

package main

func shouldDebug() bool {           // => Exists only in non-debug builds
                                    // => Conditional compilation at build time
    return false
}

Key Takeaway: //go:build expression (Go 1.16+) controls compilation. -tags flag enables tags at build-time. Use for platform-specific code, feature flags, and integration tests that require external services.

Why It Matters: Build tags enable platform-specific implementations without runtime checks, where Unix syscalls compile only on Linux/Mac and Windows equivalents compile only on Windows, producing optimized binaries without dead code. Production use cases include feature flags (build with/without premium features), integration tests that require external services (skip in CI with //go:build !integration), and specialized builds (embed debug symbols only in development builds).

Example 76: Custom Sorting

Sorting requires implementing the sort.Interface or using sort.Slice(). Custom sorting enables ordering by different fields or complex criteria.

Code:

package main

import (
    "fmt"
    "sort"
)

func main() {
    users := []User{                              // => Create slice of User structs
        {Name: "Alice", Age: 30, Score: 95},      // => User{Name: "Alice", Age: 30, Score: 95}
        {Name: "Bob", Age: 25, Score: 87},        // => User{Name: "Bob", Age: 25, Score: 87}
        {Name: "Charlie", Age: 30, Score: 92},    // => User{Name: "Charlie", Age: 30, Score: 92}
    }                                             // => users is []User with 3 elements

    // Sort by name
    sort.Sort(ByName(users))                      // => Convert []User to ByName type
                                                  // => ByName implements sort.Interface (Len, Swap, Less)
                                                  // => Sorts in-place by name (Alice, Bob, Charlie)
    fmt.Println("By name:", users)
                                                  // => Output: By name: [{Alice 30 95} {Bob 25 87} {Charlie 30 92}]

    // Sort by score descending
    sort.Sort(ByScoreDesc(users))                 // => Convert to ByScoreDesc type
                                                  // => Less compares scores with > (descending order)
                                                  // => Sorts in-place: Alice(95), Charlie(92), Bob(87)
    fmt.Println("By score desc:", users)
                                                  // => Output: By score desc: [{Alice 30 95} {Charlie 30 92} {Bob 25 87}]

    // Custom sort with Slice (simpler for one-off sorts)
    sort.Slice(users, func(i, j int) bool {      // => sort.Slice accepts custom Less function
                                                  // => i, j are indices to compare
        if users[i].Age == users[j].Age {         // => If ages equal, compare scores
            return users[i].Score > users[j].Score
                                                  // => Age then score desc
                                                  // => Higher score comes first
        }
        return users[i].Age < users[j].Age        // => Age ascending
                                                  // => Younger users first
    })                                            // => Multi-field sort: age ASC, then score DESC
    fmt.Println("By age then score:", users)
                                                  // => Output: [{Bob 25 87} {Alice 30 95} {Charlie 30 92}]
                                                  // => Bob (25) first, then Alice/Charlie (30) sorted by score
}

type User struct {
    Name  string                                  // => User name field
    Age   int                                     // => User age field
    Score int                                     // => User score field
}

// Implement sort.Interface
type ByName []User                                // => Type alias: []User named ByName
                                                  // => Enables implementing methods on slice type

func (b ByName) Len() int { return len(b) }      // => sort.Interface: returns number of elements
                                                  // => Required for sorting algorithm
func (b ByName) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
                                                  // => sort.Interface: swaps elements at indices i, j
                                                  // => Simultaneous assignment swaps values
func (b ByName) Less(i, j int) bool { return b[i].Name < b[j].Name }
                                                  // => sort.Interface: returns true if i should come before j
                                                  // => String comparison: lexicographic order

type ByScoreDesc []User                           // => Another type alias for different sort order

func (b ByScoreDesc) Len() int { return len(b) } // => Same Len implementation
func (b ByScoreDesc) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
                                                  // => Same Swap implementation
func (b ByScoreDesc) Less(i, j int) bool { return b[i].Score > b[j].Score }
                                                  // => Different Less: uses > for descending order
                                                  // => Higher scores come first (95 before 87)

Key Takeaway: Implement sort.Interface (Len, Swap, Less) for custom sorting. Use sort.Slice() for one-off sorts with custom comparator. Implement Less such that Less(i, j) returns true if element i should come before j.

Why It Matters: This concept is fundamental to understanding the language and helps build robust, maintainable code.

Example 77: Dependency Injection

Dependency injection passes dependencies to functions/types instead of creating them internally. Enables testing with mock dependencies and decouples implementations.

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC, Brown #CA9161
graph TD
    A["UserService<br/>depends on Database"]
    B["Production:<br/>Inject RealDB"]
    C["Testing:<br/>Inject MockDB"]
    D["UserService uses<br/>Database interface"]
    E["RealDB.Query()<br/>Real SQL"]
    F["MockDB.Query()<br/>Hardcoded data"]

    A --> D
    B --> E
    C --> F
    D -.->|production| E
    D -.->|testing| F

    style A fill:#0173B2,stroke:#000,color:#fff
    style B fill:#DE8F05,stroke:#000,color:#fff
    style C fill:#DE8F05,stroke:#000,color:#fff
    style D fill:#029E73,stroke:#000,color:#fff
    style E fill:#CC78BC,stroke:#000,color:#fff
    style F fill:#CA9161,stroke:#000,color:#fff

Code:

package main

import (
    "fmt"
)

func main() {
    // Real database
    db := &RealDB{}                               // => Create real database instance
                                                  // => db is *RealDB (concrete type)
    userService := NewUserService(db)             // => Inject real DB (implicit interface conversion)
                                                  // => RealDB implements Database interface (Get method)
                                                  // => userService.db is Database interface pointing to RealDB

    fmt.Println(userService.GetUser(1))           // => Calls RealDB.Get(1) through interface
                                                  // => Output: User 1 from database

    // Mock database for testing
    mockDB := &MockDB{}                           // => Create mock database instance
                                                  // => mockDB is *MockDB (concrete type)
    userService2 := NewUserService(mockDB)        // => Inject mock DB
                                                  // => MockDB also implements Database interface
                                                  // => Same constructor, different implementation

    fmt.Println(userService2.GetUser(1))          // => Calls MockDB.Get(1) through interface
                                                  // => Output: Mock user 1
                                                  // => No changes to UserService code needed
}

// Interface for testability
type Database interface {
    Get(id int) string                            // => Single method: Get user by ID
                                                  // => Any type with Get(int) string satisfies interface
}                                                 // => Enables multiple implementations

// Real implementation
type RealDB struct{}                              // => Production implementation (connects to real DB)
                                                  // => Empty struct: no state needed for this example

func (r *RealDB) Get(id int) string {            // => Implements Database interface
                                                  // => In production: would execute SQL query
    return fmt.Sprintf("User %d from database", id)
                                                  // => Returns formatted string with user ID
}                                                 // => Simulates database retrieval

// Mock for testing
type MockDB struct{}                              // => Test implementation (no external dependencies)
                                                  // => Used in unit tests for fast, isolated testing

func (m *MockDB) Get(id int) string {            // => Implements same Database interface
                                                  // => Hardcoded test data (no real DB needed)
    return fmt.Sprintf("Mock user %d", id)        // => Returns predictable test data
                                                  // => Tests run fast without DB connection
}

// Service - depends on Database interface, not concrete type
type UserService struct {
    db Database                                   // => Depend on interface, not concrete type
                                                  // => Enables swapping RealDB/MockDB without code changes
}                                                 // => Key to dependency injection pattern

// Constructor injection
func NewUserService(db Database) *UserService {  // => Inject dependency through constructor
                                                  // => db parameter is interface (accepts any implementation)
    return &UserService{db: db}                   // => Store injected dependency in struct
                                                  // => Service doesn't know concrete type (RealDB or MockDB)
}

func (s *UserService) GetUser(id int) string {  // => Business logic method
    return s.db.Get(id)                           // => Use injected dependency
                                                  // => Calls Get on interface (dispatches to concrete type)
}                                                 // => Works with RealDB in production, MockDB in tests

Key Takeaway: Depend on interfaces, not concrete types. Pass dependencies through constructors. Enables testing with mock implementations and decouples code from specific implementations.

Why It Matters: This concept is fundamental to understanding the language and helps build robust, maintainable code.

Example 78: Subtests

Subtests organize tests hierarchically with t.Run(). Each subtest can have setup/teardown and reports individually. Subtests can run in parallel with t.Parallel().

Code:

package main

import "testing"

func TestUserService(t *testing.T) {
                                        // => Parent test function
                                        // => Contains all user service subtests
    users := setupTestData()            // => Setup shared test data for all subtests
                                        // => users is []*User with 2 elements
                                        // => Shared across all subtests in this function

    t.Run("GetUser", func(t *testing.T) {
                                        // => Subtest group: reports as TestUserService/GetUser
                                        // => Groups related GetUser tests together
        t.Run("ExistingUser", func(t *testing.T) {
                                        // => Nested: TestUserService/GetUser/ExistingUser
                                        // => Test case: find user by valid ID
            user := findUser(users, 1)  // => user is {Name: "Alice", Age: 30}
                                        // => ID 1 returns first user (1-indexed)
            if user.Name != "Alice" {   // => Assert user name is correct
                t.Errorf("Expected Alice, got %s", user.Name)
                                        // => t.Errorf marks test failed
            }                           // => Test passes (user.Name is "Alice")
        })

        t.Run("NonExistentUser", func(t *testing.T) {
                                        // => Nested: TestUserService/GetUser/NonExistent
                                        // => Test case: find user by invalid ID
            user := findUser(users, 999)
                                        // => user is nil (invalid ID)
                                        // => ID 999 exceeds user count (returns nil)
            if user != nil {            // => Assert user is nil
                t.Errorf("Expected nil, got %v", user)
            }                           // => Test passes (user is nil)
        })
    })

    t.Run("CreateUser", func(t *testing.T) {
                                        // => Separate group: TestUserService/CreateUser
                                        // => Tests user creation functionality
        newUser := User{Name: "David", Age: 28}
                                        // => newUser is User{Name: "David", Age: 28}
        created := createUser(newUser)  // => created is User{Name: "David", Age: 28}
                                        // => createUser echoes back input
        if created.Name != "David" {    // => Assert created user has correct name
            t.Errorf("Expected David, got %s", created.Name)
        }                               // => Test passes (created.Name is "David")
    })
}

// Parallel subtests
func TestParallel(t *testing.T) {   // => Test demonstrating parallel execution
                                    // => Parent function for parallel subtests
    t.Run("Sequential", func(t *testing.T) {
                                    // => Runs sequentially, can access shared state
                                    // => Does NOT call t.Parallel()
                                    // => Executes before parallel subtests
        // Sequential test code       // => Can safely access mutable shared data
    })

    t.Run("Parallel", func(t *testing.T) {
                                    // => Parallel subtest group
        t.Parallel()                // => Runs in parallel with other t.Parallel() tests
                                    // => Must NOT access shared mutable state
                                    // => Go test framework manages goroutine pool
        // Parallel test code       // => Runs concurrently with other parallel subtests
    })
}

type User struct {              // => User data structure for tests
    Name string                 // => User name field
    Age  int                    // => User age field
}

func setupTestData() []*User {  // => Returns test data: 2 users
                                // => Pointer slice for shared references
    return []*User{             // => Slice literal initialization
        {Name: "Alice", Age: 30},
                                // => First user (ID 1)
        {Name: "Bob", Age: 25}, // => Second user (ID 2)
    }
}

func findUser(users []*User, id int) *User {
                                // => Find by ID (1-indexed)
                                // => users is slice of pointers
    if id > 0 && id <= len(users) {
                                // => Validate ID is within bounds (1 to len)
        return users[id-1]      // => Returns user pointer (convert 1-indexed to 0-indexed)
    }
    return nil                  // => Returns nil for invalid ID (out of range)
}

func createUser(u User) User {  // => Simplified: echoes back user
                                // => Production would save to database
    return u                    // => Returns same user unchanged
}

Key Takeaway: t.Run() creates subtests that report individually. Use for organizing tests into logical groups. t.Parallel() enables parallel execution for tests without shared state. Subtests can have separate setup/teardown.

Why It Matters: Subtests organize complex test suites hierarchically with independent reporting, where TestUserService/GetUser/ExistingUser and TestUserService/GetUser/NonExistent run as separate test cases with isolated setup/teardown. Combined with t.Parallel(), subtests enable safe concurrent test execution, reducing CI time from minutes to seconds for large test suites while maintaining clarity through structured test names and granular failure reporting.

Example 79: Mocking with Interfaces

Testing requires isolating code under test from external dependencies. Mock implementations of interfaces enable testing without real services like databases or APIs.

Code:

package main

import (
    "testing"
)

func TestUserRepository(t *testing.T) {
                                                   // => Test using mock implementation
                                                   // => No real database required for testing
    mock := &MockStorage{                         // => mock implements Storage interface
                                                   // => Pointer to MockStorage struct
        data: map[int]User{                       // => Initialize internal map with test data
            1: {ID: 1, Name: "Alice"},            // => Seed test data (no database needed)
                                                   // => ID 1 maps to Alice
        },
    }

    repo := NewUserRepository(mock)               // => Inject mock into repository
                                                   // => Repository doesn't know it's a mock
                                                   // => Dependency injection pattern
                                                   // => repo uses Storage interface, not concrete type

    user, err := repo.Get(1)                      // => Internally calls mock.Get(1)
                                                   // => Returns User{ID: 1, Name: "Alice"}
                                                   // => No real database query executed
    if err != nil {                               // => Check for errors
        t.Errorf("Unexpected error: %v", err)     // => Fail test if error occurred
                                                   // => %v formats error
    }
    if user.Name != "Alice" {                     // => Assert user name is correct
        t.Errorf("Expected Alice, got %s", user.Name)
                                                   // => Fail test if name doesn't match
    }                                             // => Test passes (user.Name is "Alice")
}

type User struct {                                // => User entity for testing
    ID   int                                      // => User identifier
    Name string                                   // => User name
}

// Interface for testability
type Storage interface {                          // => Abstraction for data storage
                                                   // => Enables dependency injection
    Get(id int) (User, error)                     // => Both Database and MockStorage implement this
                                                   // => Retrieve user by ID
    Save(u User) error                            // => Store user
                                                   // => Returns error if save fails
}

// Real storage
type Database struct{}                            // => Production implementation
                                                   // => Would contain connection pool, config

func (d *Database) Get(id int) (User, error) {   // => Real: executes database query
                                                   // => Receiver: *Database
    // Real database query                        // => SELECT * FROM users WHERE id = ?
                                                   // => Slow (network latency, disk I/O)
    return User{}, nil                            // => Placeholder return (actual DB query omitted)
}

func (d *Database) Save(u User) error {          // => Real: writes to database
                                                   // => Receiver: *Database
    // Real database write                        // => INSERT INTO users VALUES (...)
                                                   // => Slow (network, disk I/O)
    return nil                                    // => Placeholder return
}

// Mock storage
type MockStorage struct {                         // => Test double for Storage interface
                                                   // => No external dependencies
    data map[int]User                             // => In-memory storage (fast, no dependencies)
                                                   // => Map from ID to User
}

func (m *MockStorage) Get(id int) (User, error) {
                                                   // => Mock: lookup in map
                                                   // => Receiver: *MockStorage
    if user, ok := m.data[id]; ok {               // => Check if ID exists in map
                                                   // => ok is true if key exists
        return user, nil                          // => Instant (no database query)
                                                   // => Return found user
    }
    return User{}, nil                            // => Return zero value User if not found
}

func (m *MockStorage) Save(u User) error {       // => Mock: store in map
                                                   // => Receiver: *MockStorage
    m.data[u.ID] = u                              // => No database write (just map assignment)
                                                   // => Store user in map
    return nil                                    // => Fast, predictable
                                                   // => Always succeeds in tests
}

// Repository - depends on Storage interface
type UserRepository struct {                      // => Business logic layer
                                                   // => Coordinates storage operations
    storage Storage                               // => Can be Database or MockStorage
                                                   // => Interface field enables dependency injection
}

func NewUserRepository(s Storage) *UserRepository {
                                                   // => Constructor injection
                                                   // => s can be any Storage implementation
    return &UserRepository{storage: s}            // => Accepts any Storage implementation
                                                   // => Returns pointer to new UserRepository
}

func (r *UserRepository) Get(id int) (User, error) {
                                                   // => Retrieve user from storage
                                                   // => Receiver: *UserRepository
    return r.storage.Get(id)                      // => Delegates to injected storage
                                                   // => Works with both implementations
                                                   // => Calls Database.Get or MockStorage.Get
}

Key Takeaway: Mock implementations satisfy interfaces. Inject mocks into code under test. Mocks enable testing without real external services. Use simple in-memory mocks for fast tests.

Why It Matters: This concept is fundamental to understanding the language and helps build robust, maintainable code.

Example 80: Fuzzing (Go 1.18+)

Fuzzing automatically generates random inputs to find edge cases and crashes. Go’s built-in fuzzing runs test function with generated and seed values.

Code:

package main

import (
    "testing"
    "unicode/utf8"
)

// Run fuzzing: go test -fuzz=FuzzParseInt
                                                  // => Fuzzer generates random inputs to find crashes
                                                  // => Command runs fuzzing until stopped (Ctrl+C)
func FuzzParseInt(f *testing.F) {                 // => f is *testing.F (fuzzing controller)
                                                  // => Function name must start with Fuzz
    // Seed values - good test cases to always include
    f.Add("0")                                    // => Seed corpus: known-good test cases
                                                  // => Fuzzer starts with seeds
    f.Add("42")                                   // => Fuzzer starts with these, then mutates
                                                  // => Generates variations of seed inputs
    f.Add("-100")                                 // => Negative number seed
                                                  // => Tests edge case handling
    f.Add("2147483647")                           // => Max int32 boundary case
                                                  // => Tests overflow scenarios

    f.Fuzz(func(t *testing.T, input string) {     // => input is randomly generated string
                                                   // => Coverage-guided: explores new code paths
                                                   // => Fuzzer mutates seeds and prior inputs
        if len(input) == 0 {                      // => Check for empty string
                                                   // => Early return to skip invalid input
            return                                // => Skip empty input (not interesting)
        }

        result, err := parseInt(input)            // => Test with generated input
                                                   // => May receive bizarre strings
        _ = result                                // => Fuzzer calls thousands of times per second
                                                   // => Discard result (only checking for crashes)
        _ = err                                   // => Discard error (fuzzer looks for panics)
        // No assertion - fuzzer looks for panics and crashes
                                                   // => Fuzzing finds code that panics or crashes
    })
}

// Fuzzing UTF-8 strings
func FuzzValidUTF8(f *testing.F) {                // => Test UTF-8 handling
                                                  // => Ensures code handles all valid UTF-8
    f.Add("hello")                                // => Seed: ASCII string
                                                  // => Simple ASCII characters
    f.Add("世界")                                  // => Seed: Multi-byte UTF-8 characters
                                                  // => Chinese characters (3 bytes each)

    f.Fuzz(func(t *testing.T, input string) {     // => Fuzzer generates UTF-8 strings
                                                   // => Coverage-guided mutation
        if !utf8.ValidString(input) {             // => Check if valid UTF-8
                                                   // => All generated strings should be valid
            t.Errorf("Invalid UTF-8: %v", input)  // => Should NOT happen (fuzzer generates valid UTF-8)
                                                   // => Reports failure if invalid UTF-8 found
        }                                         // => Test verifies utf8.ValidString always true
    })
}

func parseInt(s string) (int, error) {            // => Simple parser for demonstration
                                                  // => Production parser would handle all cases
    if s == "0" {                                 // => Special case for zero
        return 0, nil                             // => Return 0 and no error
    }
    return 1, nil                                 // => Simplified (real parser would parse digits)
                                                  // => Always returns 1 for demo purposes
}

Key Takeaway: Fuzzing tests provide generated inputs. Seed values with f.Add() include important test cases. The f.Fuzz() function receives generated inputs. Fuzzer looks for panics and crashes in your code.

Why It Matters: This concept is fundamental to understanding the language and helps build robust, maintainable code.

Example 81: CGO Basics

CGO enables calling C from Go. Use when you need external C libraries or performance-critical code. CGO adds complexity - prefer pure Go when possible.

  %% Go-C interop boundary and type conversion
graph TD
    A["Go Code<br/>goString string"]
    B["C.CString()<br/>Type Conversion"]
    C["C Memory<br/>*char (malloc)"]
    D["C Function<br/>strlen_c()"]
    E["Return Value<br/>C.int"]
    F["Type Conversion<br/>int()"]
    G["Go Code<br/>length int"]
    H["C.free()<br/>Cleanup"]

    A -->|"Go string"| B
    B -->|"*C.char"| C
    C -->|"Pass pointer"| D
    D -->|"C.int result"| E
    E -->|"Convert"| F
    F -->|"Go int"| G
    C -.->|"Must free!"| H

    style A fill:#0173B2,stroke:#000,color:#fff
    style B fill:#DE8F05,stroke:#000,color:#fff
    style C fill:#CA9161,stroke:#000,color:#fff
    style D fill:#CC78BC,stroke:#000,color:#fff
    style E fill:#CA9161,stroke:#000,color:#fff
    style F fill:#DE8F05,stroke:#000,color:#fff
    style G fill:#029E73,stroke:#000,color:#fff
    style H fill:#CC78BC,stroke:#000,color:#fff,stroke-width:3px,stroke-dasharray: 5 5

Code:

package main

/*
#include <stdio.h>                             // => C standard I/O library
#include <string.h>                            // => C string functions (strlen)

int add(int a, int b) {                        // => C function definition
    return a + b;                              // => Returns sum of a and b
}

int strlen_c(const char* s) {                  // => C function wrapper for strlen
    return strlen(s);                          // => Returns string length
}
*/
import "C"                                      // => Pseudo-package for CGO
                                                // => Must import immediately after comment block
                                                // => Enables calling C code from Go

import (
    "fmt"
)

func main() {
    // Call C function
    result := C.add(10, 20)                     // => Call C add function defined above
                                                // => C.add is Go binding to C function
                                                // => Arguments automatically converted (Go int to C int)
                                                // => result is C.int type
    fmt.Println("C.add(10, 20) =", result)      // => Output: C.add(10, 20) = 30
                                                // => fmt handles C.int printing

    // String from Go to C
    goString := "Hello"                         // => goString is Go string type
                                                // => Go strings are not null-terminated
    cString := C.CString(goString)              // => Convert Go string to C string
                                                // => Allocates memory with malloc
                                                // => cString is *C.char (pointer to C string)
                                                // => Null-terminated C-style string
    defer C.free(cString)                       // => Must free C-allocated memory
                                                // => C.free wraps stdlib free()
                                                // => Deferred: executes when function exits
                                                // => CRITICAL: forgetting this causes memory leak

    length := C.strlen_c(cString)               // => Call C strlen_c function
                                                // => cString passed as const char*
                                                // => length is C.int type (value 5)
    fmt.Println("Length:", length)              // => Output: Length: 5
                                                // => Demonstrates string passing to C

    // Complex example - calculate from Go
    a := 15                                     // => a is Go int
    b := 25                                     // => b is Go int
    sum := int(C.add(C.int(a), C.int(b)))       // => Multiple type conversions
                                                // => C.int(a) converts Go int to C int
                                                // => C.int(b) converts Go int to C int
                                                // => C.add returns C.int
                                                // => int(...) converts C.int back to Go int
                                                // => sum is 40 (type: Go int)
    fmt.Println("Sum:", sum)                    // => Output: Sum: 40
                                                // => Demonstrates type conversion dance
}

Key Takeaway: CGO imports C code and calls C functions. Use C.CString() to convert Go strings to C strings. Remember to free() C-allocated memory. CGO is complex - use only when necessary.

Why It Matters: CGO enables leveraging optimized C libraries (SQLite embedded database, OpenSSL cryptography) and platform-specific APIs unavailable in pure Go, where performance-critical code or legacy system integration requires calling C. However, CGO disables cross-compilation, prevents stack traces across the C boundary, and introduces manual memory management (remember to free!), making it a last resort after evaluating pure-Go alternatives for portability and safety.

Example 82: Workspaces (Go 1.18+)

Workspaces enable multi-module development. Develop multiple modules together without publishing intermediate versions. Useful for monorepos or coordinating multiple packages.

Code:

// go.work file (workspace configuration)
go 1.21                    // => Minimum Go version (workspaces added in Go 1.18)
                            // => Workspace feature requires Go 1.18+

use (                      // => Declare modules (each must have go.mod)
                            // => All modules use local versions
    ./cmd/api              // => Changes reflected immediately in dependent modules
                            // => Relative path from go.work location
    ./cmd/cli
    ./libs/common
)

// Create workspace: go work init ./cmd/api ./cmd/cli ./libs/common  (generates go.work)
                                                                      // => Initializes workspace with modules
// Add module: go work use ./new-module                              (adds to workspace)
                                                                      // => Appends to use() block
// Remove module: go work edit -dropuse=./cmd/cli                    (module still exists)
                                                                      // => Removes from workspace, doesn't delete

Directory structure:

project/
├── go.work            (created with: go work init ./cmd/api ./cmd/cli ./libs/common)
├── cmd/api/go.mod     (module: example.com/api)
├── cmd/cli/go.mod     (module: example.com/cli)
└── libs/common/go.mod (module: example.com/common shared)

Benefits: Develop multiple modules together without publishing. Changes in libs/common reflected immediately in dependent modules. Test all modules together with go test ./....

Key Takeaway: Workspaces allow multi-module development without publishing. Define workspace with go.work file. Use use() to include local modules. All modules use local versions instead of published versions.

Why It Matters: Workspaces enable monorepo development where multiple interdependent modules evolve together without publishing intermediate versions, solving the problem of testing changes across libs/common and cmd/api before committing. Production teams use workspaces to develop coordinated updates across packages, test breaking changes before release, and maintain local overrides for dependencies, all while preserving independent module versioning for public releases.

Example 83: Memory Profiling

Memory profiling identifies allocations and memory leaks. The runtime/pprof package enables capturing profiles. Analyze with go tool pprof.

Code:

package main

import (
    "os"
    "runtime/pprof"
)

func main() {
    // Memory profiling
    f, err := os.Create("mem.prof")               // => Create profile file in current directory
                                                   // => Returns *os.File and error
    if err != nil {                               // => Check if file creation failed
        panic(err)                                // => Abort if cannot create profile
    }
    defer f.Close()                               // => Ensure file closed and flushed
                                                   // => Deferred to guarantee cleanup

    pprof.WriteHeapProfile(f)                     // => Capture heap profile (current memory state)
                                                   // => Snapshots heap allocations to mem.prof
                                                   // => Shows current memory usage

    // Run program: go run main.go               // => Execute to generate profile
    // Then analyze: go tool pprof mem.prof       // => Interactive profiler tool
                                                   // => Opens REPL for profile analysis
    // Commands in pprof:
    //   top    - shows top memory allocators      // => Displays functions with most allocations
    //   list   - shows source code with allocations // => Source-level view of allocations
    //   web    - generates graph (requires Graphviz) // => Visual call graph

    // CPU profiling
    cpuFile, _ := os.Create("cpu.prof")           // => Create CPU profile file
                                                   // => Ignoring error for demo (bad practice)
    defer cpuFile.Close()                         // => Ensure CPU profile written

    pprof.StartCPUProfile(cpuFile)                // => Start profiling CPU usage
                                                   // => Samples CPU every 10ms, records call stacks
                                                   // => Begins recording which functions consume CPU
    defer pprof.StopCPUProfile()                  // => MUST call Stop to finalize profile
                                                   // => Writes collected samples to file

    expensiveComputation()                        // => Execute work to profile
                                                   // => CPU profiler captures where time spent
}

func expensiveComputation() {                     // => Function to profile CPU usage
                                                   // => Demonstrates CPU profiling
    for i := 0; i < 1000000; i++ {                // => Loop 1 million times
                                                   // => Creates measurable CPU load
        _ = i * i                                 // => Prevents compiler optimization
                                                   // => Profiler identifies this as hotspot
                                                   // => Multiplication shows in CPU profile
    }
}

Key Takeaway: Use pprof.WriteHeapProfile() to capture memory allocations. Use pprof.StartCPUProfile() for CPU profiling. Analyze profiles with go tool pprof. Profile helps identify bottlenecks and memory leaks.

Why It Matters: This concept is fundamental to understanding the language and helps build robust, maintainable code.

Example 84: Race Detector

Go’s race detector identifies concurrent access to shared memory without synchronization. Run with -race flag during development and testing.

Code:

package main

import (
    "fmt"
    "sync"
)

func main() {
    var counter int                         // => Shared variable (no protection)
                                             // => Accessed by multiple goroutines
    var wg sync.WaitGroup                   // => WaitGroup to wait for completion
                                             // => Tracks goroutine completion

    // Race condition - multiple goroutines modify counter without sync
    for i := 0; i < 10; i++ {               // => Spawn 10 concurrent goroutines
                                             // => All access counter simultaneously
        wg.Add(1)                           // => Increment WaitGroup counter
                                             // => Before spawning goroutine
        go func() {                         // => Goroutine closure
                                             // => Shares counter variable (DANGER!)
            defer wg.Done()                 // => Decrement WaitGroup when done
            counter++                       // => DATA RACE! Read-modify-write not atomic
                                             // => Race detector will flag this line
                                             // => Multiple goroutines read/write simultaneously
        }()
    }

    wg.Wait()                               // => Block until all goroutines finish
                                             // => Wait for counter to reach 0
    fmt.Println("Counter:", counter)        // => Unpredictable (should be 10, race may lose increments)
                                             // => Output varies: might be 7, 9, or 10

    // Run with: go run -race main.go       // => Enables race detector
                                             // => Reports file:line and goroutine stacks
                                             // => Adds 5-10x overhead (dev/test only)

    // Fixed version with mutex
    var mu sync.Mutex                       // => Mutex for synchronization
                                             // => Protects critical section
    counter = 0                             // => Reset counter to 0
                                             // => Start fresh for safe version

    for i := 0; i < 10; i++ {               // => Spawn 10 goroutines again
        wg.Add(1)                           // => Increment WaitGroup
        go func() {                         // => Goroutine with mutex protection
            defer wg.Done()                 // => Ensure Done() called
            mu.Lock()                       // => Acquire lock (blocks if held by another)
                                             // => Only one goroutine in critical section
            counter++                       // => Protected by mutex (safe)
                                             // => Read-modify-write is now atomic
            mu.Unlock()                     // => Release lock
                                             // => Allow next goroutine to acquire
        }()
    }

    wg.Wait()                               // => Wait for all goroutines to complete
    fmt.Println("Counter (safe):", counter) // => Always 10 (race-free)
                                             // => Output: Counter (safe): 10
                                             // => Deterministic result
}

Key Takeaway: Run tests with -race flag to detect concurrent data access without synchronization. Race detector finds most (but not all) race conditions. Use mutexes, channels, or atomic operations to fix races.

Why It Matters: This concept is fundamental to understanding the language and helps build robust, maintainable code.

Example 85: Go Best Practices Synthesis

Go development succeeds by following core principles: explicit error handling, simple concurrency with channels, composition with interfaces, and extensive testing. This final example summarizes key practices for production-ready code.

Code:

package main

import (
    "context"
    "fmt"
    "net/http"
    "sync"
)

func main() {
    // Production patterns synthesis
    // 1. Error handling - explicit, not exceptions
    if err := processData(); err != nil {           // => Check error immediately
                                                    // => Never ignore errors
                                                    // => err is error interface or nil
        fmt.Printf("Error: %v\n", err)              // => Always check errors
                                                    // => Output: Error: <error message>
                                                    // => %v formats error with Error() method
    }

    // 2. Concurrency - channels and goroutines
    results := make(chan int, 10)                   // => Buffered channel (capacity 10)
                                                    // => results is chan int
                                                    // => Buffer prevents blocking on send
    go func() {                                     // => Spawn goroutine
                                                    // => Runs concurrently with main
        results <- 42                               // => Send value to channel
                                                    // => Does not block (buffer has space)
        close(results)                              // => Close channel (no more values)
                                                    // => Signals receiver
    }()
    fmt.Println(<-results)                          // => Receive from channel
                                                    // => Blocks until value available
                                                    // => Output: 42
                                                    // => Channel closed after receive

    // 3. Interfaces for composition
    var w http.ResponseWriter                       // => Depend on interfaces
                                                    // => w is nil (no concrete implementation)
                                                    // => Interface enables multiple implementations
    _ = w                                           // => Blank identifier (suppress unused warning)

    // 4. Context for cancellation
    ctx, cancel := context.WithCancel(context.Background())
                                                    // => ctx is cancellable context
                                                    // => cancel is function to cancel context
                                                    // => Background() returns empty root context
    defer cancel()                                  // => Ensure cancel called
                                                    // => Releases resources
                                                    // => Always defer cancel to prevent leaks

    var wg sync.WaitGroup                           // => WaitGroup to synchronize
                                                    // => Counter starts at 0
    wg.Add(1)                                       // => Increment counter
                                                    // => Counter is now 1
    go func() {                                     // => Spawn goroutine
                                                    // => Runs concurrently
        defer wg.Done()                             // => Decrement counter when done
                                                    // => Always use defer for Done
        <-ctx.Done()                                // => Respect cancellation
                                                    // => Blocks until context cancelled
                                                    // => ctx.Done() returns closed channel
    }()

    wg.Wait()                                       // => Block until counter reaches 0
                                                    // => Waits for goroutine to complete

    // 5. Testing - write table-driven tests
    testCases := []struct {                         // => Slice of anonymous structs
        input    int                                // => Test input
        expected int                                // => Expected output
    }{
        {1, 2},                                     // => Test case 1: input=1, expected=2
        {5, 10},                                    // => Test case 2: input=5, expected=10
    }

    for _, tc := range testCases {                  // => Iterate test cases
                                                    // => tc is each test case struct
        result := tc.input * 2                      // => Compute result
                                                    // => result is tc.input * 2
        if result != tc.expected {                  // => Compare with expected
                                                    // => Reports failure if mismatch
            fmt.Printf("Test failed: %d\n", tc.input)
                                                    // => Output: Test failed: <input>
        }
    }

    // 6. Code organization - flat structure, small packages
                                                    // => Keep packages focused and small
                                                    // => Avoid deep nesting
    // 7. Documentation - export with doc comments
                                                    // => Exported identifiers start with capital
                                                    // => Add godoc comments before exports
}

// processData - process data with error handling
func processData() error {                          // => Returns error (nil on success)
    // Production-ready pattern:
    // - Return errors explicitly
                                                    // => Never use panic for normal errors
    // - Use interfaces for dependencies
                                                    // => Enables testing with mocks
    // - Make functions testable
                                                    // => Pure functions, dependency injection
    return nil                                      // => nil indicates success
                                                    // => Return specific error for failures
}

// Production-ready server pattern
func serverPattern() {
    server := &http.Server{                         // => Create HTTP server
        Addr:    ":8080",                           // => Listen on port 8080
                                                    // => ":8080" means all interfaces
        Handler: http.HandlerFunc(handler),         // => Set request handler
                                                    // => handler is function converted to Handler
    }

    // Graceful shutdown
    go func() {                                     // => Run server in goroutine
                                                    // => Allows shutdown handling
        if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
                                                    // => Start server (blocking call)
                                                    // => Returns error on failure
                                                    // => http.ErrServerClosed is expected on shutdown
            fmt.Printf("Server error: %v\n", err)   // => Log unexpected errors
                                                    // => Output: Server error: <error>
        }
    }()

    // Shutdown handling (signal, timeout, cleanup)
                                                    // => Listen for OS signals (SIGINT, SIGTERM)
                                                    // => Call server.Shutdown(ctx) with timeout
                                                    // => Perform cleanup (close DB, flush logs)
}

func handler(w http.ResponseWriter, r *http.Request) {
                                                    // => HTTP request handler
                                                    // => w writes response
                                                    // => r contains request details
    // Handler pattern:
    // - Check request validity
                                                    // => Validate headers, body, parameters
    // - Perform work
                                                    // => Business logic here
    // - Return appropriate status
                                                    // => Use w.WriteHeader() for status codes
    // - Log important events
                                                    // => Log errors, slow requests, security events
    fmt.Fprint(w, "OK")                             // => Write response body
                                                    // => Default status is 200 OK
}

Key Takeaway: Go best practices emphasize explicit error handling, simple concurrency with channels, composition through interfaces, extensive testing, and clear code organization. These patterns make Go code reliable, maintainable, and performant in production environments.

Why It Matters: This concept is fundamental to understanding the language and helps build robust, maintainable code.

Last updated