# Go Data Structures & Algorithmic Performance Guide

## Container Selection

### Slice (`[]T`)
- **Use for**: Ordered collections, iteration, stack (append/pop from end)
- **Lookup**: O(n) linear scan
- **Append**: O(1) amortized (O(n) when growing)
- **Insert/Delete at front**: O(n) — shifts all elements
- **Memory**: Contiguous, cache-friendly for iteration
- **Tip**: Pre-allocate with `make([]T, 0, n)` when size is known

### Map (`map[K]V`)
- **Use for**: Key-value lookup, set membership, deduplication
- **Lookup/Insert/Delete**: O(1) average
- **Iteration order**: Random (not insertion order)
- **Memory**: Higher per-element overhead than slice
- **Tip**: Pre-size with `make(map[K]V, n)` to avoid rehashing

### Slice vs Map crossover for lookup
- **<8 items**: Linear scan of slice is faster (cache locality wins)
- **8-20 items**: Map starts winning for lookup
- **>20 items**: Map is clearly faster for lookup

### sync.Map
- **Use for**: Read-heavy concurrent access (many goroutines reading, rare writes)
- **NOT for**: Write-heavy workloads — use sharded map + `sync.RWMutex` instead
- **Why**: sync.Map optimizes for stable keys; writes cause full lock

### Strings.Builder
- **Use for**: Building strings incrementally in a loop
- **NOT**: `+` concatenation in a loop (O(n^2) allocations)
- **NOT**: `fmt.Sprintf` for simple concatenation (reflect overhead)

## Algorithmic Patterns

### Replace nested loops with map index
```go
// BAD: O(n*m)
for _, a := range listA {
    for _, b := range listB {
        if a.ID == b.ID { /* match */ }
    }
}

// GOOD: O(n+m)
index := make(map[string]*ItemB, len(listB))
for _, b := range listB {
    index[b.ID] = b
}
for _, a := range listA {
    if b, ok := index[a.ID]; ok { /* match */ }
}
```

### Pre-allocate slices
```go
// BAD: grows dynamically, multiple allocations
var result []string
for _, item := range items {
    result = append(result, item.Name)
}

// GOOD: single allocation
result := make([]string, 0, len(items))
for _, item := range items {
    result = append(result, item.Name)
}
```

### Avoid repeated map lookups
```go
// BAD: two lookups
if _, ok := m[key]; ok {
    val := m[key]
}

// GOOD: one lookup
if val, ok := m[key]; ok {
    // use val
}
```

### Sort once, binary search many
```go
// BAD: linear search in loop
for _, query := range queries {
    for _, item := range sorted { // O(n) each time
        if item == query { break }
    }
}

// GOOD: binary search
sort.Strings(sorted)
for _, query := range queries {
    i := sort.SearchStrings(sorted, query) // O(log n) each time
}
```

## Go-Specific Performance Patterns

### Avoid reflect in hot paths
`reflect` is both CPU-expensive and allocation-heavy. Replace with:
- Type switches for known types
- Code generation (go generate)
- Generics (Go 1.18+)

### Compile regexp once
```go
// BAD: recompiles every call
func match(s string) bool {
    re := regexp.MustCompile(`pattern`)
    return re.MatchString(s)
}

// GOOD: compile once at package level
var rePattern = regexp.MustCompile(`pattern`)
func match(s string) bool {
    return rePattern.MatchString(s)
}
```

### Use strconv instead of fmt for simple conversions
```go
// BAD: uses reflect internally
s := fmt.Sprintf("%d", n)

// GOOD: direct conversion
s := strconv.Itoa(n)
```

### Avoid interface{} / any when concrete type is known
```go
// BAD: interface boxing allocates
func process(items []interface{}) { ... }

// GOOD: use concrete type or generics
func process(items []Item) { ... }
func process[T Item](items []T) { ... }  // Go 1.18+
```

**Benchmark**: Interface boxing adds ~11% CPU overhead for large structs due to allocation and indirection.

## Batching Operations

When individual operations are expensive (I/O, RPC, DB writes), batch them to reduce per-operation overhead:

```go
type Batcher[T any] struct {
    mu     sync.Mutex
    buffer []T
    size   int
    flush  func([]T)
}

func (b *Batcher[T]) Add(item T) {
    b.mu.Lock()
    defer b.mu.Unlock()
    b.buffer = append(b.buffer, item)
    if len(b.buffer) >= b.size {
        b.flush(b.buffer)
        b.buffer = b.buffer[:0]
    }
}
```

**Benchmark impact**:
- File I/O batching: **12× throughput** improvement (12.7ms → 994µs/op)
- Crypto (SHA256) batching: **~2× faster** (1.23ms → 675µs/op), allocs reduced 75×
- In-memory batching: Allocs reduced 50× (minor speed change)

**Warning**: Batching introduces data loss risk — if the app crashes before flush, buffered data is lost. Use periodic flushes or persistent buffers for critical data.

## Atomic Operations vs Mutexes

For simple counters, flags, and state transitions, atomics outperform mutexes:

```go
// Atomic counter (80.4 ns/op — 27% faster than mutex)
var counter atomic.Int64
counter.Add(1)

// Atomic flag for shutdown signal
var shutdown atomic.Int32
shutdown.Store(1)
if shutdown.Load() == 1 { /* stop */ }

// CAS for lock-free data structures
var head atomic.Pointer[node]
func push(n *node) {
    for {
        old := head.Load()
        n.next = old
        if head.CompareAndSwap(old, n) { return }
    }
}
```

**Benchmark**: Atomic increment: 80.4 ns/op vs Mutex increment: 110.7 ns/op (**27% faster**).

**When to use atomics**: Counters, flags, simple state machines, lock-free stacks/queues.
**When to use mutexes**: Complex shared state, multi-step critical sections, maintaining invariants.

### Reducing Lock Contention with Atomics

Use atomics as a fast-path filter before acquiring a lock:
```go
// Fast path: skip lock entirely if flag is unset
if atomic.LoadInt32(&someFlag) == 0 {
    return
}
mu.Lock()
defer mu.Unlock()
// expensive work...
```

## Immutable Data with atomic.Pointer

For read-heavy, rarely-written config or state, use copy-on-write with `atomic.Pointer`:

```go
type Config struct {
    Timeout time.Duration
    MaxConn int
}

var currentConfig atomic.Pointer[Config]

// Readers: lock-free, ~5ns
func getConfig() *Config {
    return currentConfig.Load()
}

// Writers: create new copy, swap atomically
func updateConfig(fn func(*Config) *Config) {
    for {
        old := currentConfig.Load()
        new := fn(old)
        if currentConfig.CompareAndSwap(old, new) { return }
    }
}
```

## Lazy Initialization

### sync.Once (recommended)

```go
var resource *MyResource
var once sync.Once

func getResource() *MyResource {
    once.Do(func() {
        resource = expensiveInit()
    })
    return resource
}
```

**Cost**: ~1ns after first call (fast-path check). Handles panics correctly.

### sync.OnceValue (Go 1.21+)

```go
var getResource = sync.OnceValue(func() *MyResource {
    return expensiveInit()
})
// Usage: res := getResource()
```

Cleaner API, returns the value directly.

**Warning**: Avoid rolling your own atomic-based init unless you need retryable initialization. The three-state protocol (0=untouched, 1=in-progress, 2=done) is error-prone — if `expensiveInit()` panics, waiting goroutines spin forever.
