> ## Documentation Index
> Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Interview Preparation

> Common Go interview questions, coding challenges, and system design topics

# Go Interview Preparation

This chapter covers the most common Go interview topics, coding challenges, and system design questions you will encounter. The key to Go interviews is demonstrating that you understand *why* Go makes certain design choices, not just *how* to use its features. Interviewers at companies like Google, Uber, and Cloudflare want to see that you think about trade-offs: "Go chose X because Y, and the cost is Z."

## Language Fundamentals

### Common Interview Questions

<AccordionGroup>
  <Accordion title="What are the zero values in Go?">
    | Type                                                          | Zero Value                            |
    | ------------------------------------------------------------- | ------------------------------------- |
    | `int`, `float64`                                              | `0`                                   |
    | `string`                                                      | `""` (empty string)                   |
    | `bool`                                                        | `false`                               |
    | `pointer`, `slice`, `map`, `channel`, `function`, `interface` | `nil`                                 |
    | `struct`                                                      | All fields set to their zero values   |
    | `array`                                                       | All elements set to their zero values |
  </Accordion>

  <Accordion title="What's the difference between arrays and slices?">
    **Arrays:**

    * Fixed size, part of the type: `[5]int` ≠ `[10]int`
    * Value type - copied when passed
    * Size known at compile time

    **Slices:**

    * Dynamic size, backed by an array
    * Reference type - contains pointer to underlying array
    * Has length and capacity
    * Can grow with `append()`

    ```go theme={null}
    arr := [3]int{1, 2, 3}  // Array
    slc := []int{1, 2, 3}   // Slice
    slc = append(slc, 4)    // Can grow
    ```
  </Accordion>

  <Accordion title="How does `defer` work?">
    * Deferred calls are executed in LIFO order when the function returns
    * Arguments are evaluated immediately, not when deferred call runs
    * Commonly used for cleanup (closing files, unlocking mutexes)

    ```go theme={null}
    func example() {
        defer fmt.Println("third")
        defer fmt.Println("second")
        fmt.Println("first")
    }
    // Output: first, second, third

    // Arguments evaluated immediately
    x := 10
    defer fmt.Println(x)  // Prints 10
    x = 20
    ```
  </Accordion>

  <Accordion title="Explain the difference between make and new">
    * `new(T)`: Allocates zeroed storage for type T, returns `*T`
    * `make(T, args)`: Creates and initializes slices, maps, channels only

    ```go theme={null}
    // new returns pointer to zeroed value
    p := new(int)     // *int pointing to 0
    s := new([]int)   // *[]int pointing to nil slice

    // make initializes the type
    slice := make([]int, 5, 10)  // len=5, cap=10
    m := make(map[string]int)    // initialized map
    ch := make(chan int, 10)     // buffered channel
    ```
  </Accordion>

  <Accordion title="What are interface{} and any?">
    `interface{}` (or `any` since Go 1.18) is the empty interface that all types implement. Used for:

    * Generic containers before generics
    * JSON unmarshaling
    * Printf-style functions

    Requires type assertions to use:

    ```go theme={null}
    func printValue(v any) {
        switch val := v.(type) {
        case int:
            fmt.Printf("int: %d\n", val)
        case string:
            fmt.Printf("string: %s\n", val)
        default:
            fmt.Printf("unknown: %v\n", val)
        }
    }
    ```
  </Accordion>

  <Accordion title="How are methods different from functions?">
    Methods have a receiver - they're bound to a type:

    ```go theme={null}
    // Function
    func Add(a, b int) int {
        return a + b
    }

    // Method with value receiver
    func (c Calculator) Add(a, b int) int {
        return a + b
    }

    // Method with pointer receiver (can modify receiver)
    func (c *Calculator) SetPrecision(p int) {
        c.precision = p  // Modifies the actual instance
    }
    ```

    Use pointer receivers when:

    * You need to modify the receiver
    * The receiver is large (avoid copying)
    * Consistency with other methods on the type
  </Accordion>

  <Accordion title="Explain Go's memory model">
    * Go uses garbage collection (concurrent, tri-color mark-and-sweep)
    * Stack: Local variables, function calls (fast, automatic)
    * Heap: Escaped variables, dynamically sized data (GC managed)
    * Escape analysis determines stack vs heap allocation

    ```bash theme={null}
    # Check escape analysis
    go build -gcflags="-m" ./...
    ```
  </Accordion>
</AccordionGroup>

***

## Concurrency Questions

<AccordionGroup>
  <Accordion title="What are goroutines and how do they differ from threads?">
    **Goroutines:**

    * Lightweight (2KB initial stack)
    * Managed by Go runtime
    * M:N scheduling (many goroutines on few OS threads)
    * Fast context switching
    * Can have thousands running

    **OS Threads:**

    * Heavy (1-2MB stack)
    * Managed by OS kernel
    * Expensive context switches
    * Limited by OS resources
  </Accordion>

  <Accordion title="How do channels work?">
    Channels are typed conduits for communication between goroutines:

    ```go theme={null}
    // Unbuffered - synchronous
    ch := make(chan int)
    go func() { ch <- 42 }()  // Blocks until received
    val := <-ch               // Blocks until sent

    // Buffered - asynchronous up to capacity
    ch := make(chan int, 10)
    ch <- 1  // Doesn't block (buffer not full)

    // Directional channels
    func send(ch chan<- int) { ch <- 1 }    // Send-only
    func recv(ch <-chan int) { <-ch }       // Receive-only

    // Closing channels
    close(ch)
    val, ok := <-ch  // ok is false if closed
    ```
  </Accordion>

  <Accordion title="What is a data race and how do you prevent it?">
    Data race occurs when multiple goroutines access shared data concurrently with at least one write.

    **Prevention methods:**

    ```go theme={null}
    // 1. Mutex
    var mu sync.Mutex
    mu.Lock()
    counter++
    mu.Unlock()

    // 2. Channels (share by communicating)
    updates := make(chan int)
    go func() {
        for val := range updates {
            counter = val
        }
    }()

    // 3. Atomic operations
    var counter atomic.Int64
    counter.Add(1)

    // 4. sync.Map for concurrent map access
    var m sync.Map
    m.Store("key", "value")

    // Detect races
    go run -race main.go
    go test -race ./...
    ```
  </Accordion>

  <Accordion title="Explain select statement">
    `select` waits on multiple channel operations:

    ```go theme={null}
    select {
    case msg := <-ch1:
        fmt.Println("received from ch1:", msg)
    case ch2 <- value:
        fmt.Println("sent to ch2")
    case <-time.After(time.Second):
        fmt.Println("timeout")
    default:
        fmt.Println("no channel ready")
    }
    ```

    * Blocks until one case can proceed
    * Random selection if multiple ready
    * `default` makes it non-blocking
  </Accordion>

  <Accordion title="What is context and how is it used?">
    Context carries deadlines, cancellation signals, and request-scoped values:

    ```go theme={null}
    // Cancellation
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    // Timeout
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    // Values
    ctx = context.WithValue(ctx, "userID", "123")

    // Check cancellation
    select {
    case <-ctx.Done():
        return ctx.Err()
    default:
        // continue work
    }
    ```
  </Accordion>

  <Accordion title="How do you handle goroutine leaks?">
    Goroutine leaks occur when goroutines are blocked forever. Prevention:

    ```go theme={null}
    // Always use context for cancellation
    func worker(ctx context.Context) {
        for {
            select {
            case <-ctx.Done():
                return  // Clean exit
            default:
                doWork()
            }
        }
    }

    // Close channels to signal completion
    func producer(done <-chan struct{}) <-chan int {
        out := make(chan int)
        go func() {
            defer close(out)
            for i := 0; ; i++ {
                select {
                case <-done:
                    return
                case out <- i:
                }
            }
        }()
        return out
    }

    // Monitor goroutine count
    runtime.NumGoroutine()
    ```
  </Accordion>
</AccordionGroup>

***

## Coding Challenges

### Implement a LRU Cache

An LRU (Least Recently Used) cache is one of the most common interview coding challenges. The key insight is using a hash map for O(1) lookups combined with a doubly linked list for O(1) insertion, deletion, and reordering. Every `Get` moves the accessed node to the front; when capacity is exceeded, the node at the back (least recently used) is evicted.

```go theme={null}
type LRUCache struct {
    capacity int
    cache    map[int]*Node
    head     *Node
    tail     *Node
}

type Node struct {
    key, value int
    prev, next *Node
}

func Constructor(capacity int) LRUCache {
    head := &Node{}
    tail := &Node{}
    head.next = tail
    tail.prev = head
    
    return LRUCache{
        capacity: capacity,
        cache:    make(map[int]*Node),
        head:     head,
        tail:     tail,
    }
}

func (c *LRUCache) Get(key int) int {
    if node, ok := c.cache[key]; ok {
        c.moveToFront(node)
        return node.value
    }
    return -1
}

func (c *LRUCache) Put(key, value int) {
    if node, ok := c.cache[key]; ok {
        node.value = value
        c.moveToFront(node)
        return
    }
    
    node := &Node{key: key, value: value}
    c.cache[key] = node
    c.addToFront(node)
    
    if len(c.cache) > c.capacity {
        removed := c.removeLast()
        delete(c.cache, removed.key)
    }
}

func (c *LRUCache) addToFront(node *Node) {
    node.prev = c.head
    node.next = c.head.next
    c.head.next.prev = node
    c.head.next = node
}

func (c *LRUCache) remove(node *Node) {
    node.prev.next = node.next
    node.next.prev = node.prev
}

func (c *LRUCache) moveToFront(node *Node) {
    c.remove(node)
    c.addToFront(node)
}

func (c *LRUCache) removeLast() *Node {
    node := c.tail.prev
    c.remove(node)
    return node
}
```

### Implement Rate Limiter

```go theme={null}
type RateLimiter struct {
    tokens   float64
    capacity float64
    rate     float64
    lastTime time.Time
    mu       sync.Mutex
}

func NewRateLimiter(rate float64, capacity float64) *RateLimiter {
    return &RateLimiter{
        tokens:   capacity,
        capacity: capacity,
        rate:     rate,
        lastTime: time.Now(),
    }
}

func (r *RateLimiter) Allow() bool {
    r.mu.Lock()
    defer r.mu.Unlock()
    
    now := time.Now()
    elapsed := now.Sub(r.lastTime).Seconds()
    r.lastTime = now
    
    // Refill tokens
    r.tokens += elapsed * r.rate
    if r.tokens > r.capacity {
        r.tokens = r.capacity
    }
    
    if r.tokens >= 1 {
        r.tokens--
        return true
    }
    return false
}
```

### Implement Worker Pool

```go theme={null}
type Job func() error

type WorkerPool struct {
    jobs    chan Job
    results chan error
    wg      sync.WaitGroup
}

func NewWorkerPool(workers int, queueSize int) *WorkerPool {
    p := &WorkerPool{
        jobs:    make(chan Job, queueSize),
        results: make(chan error, queueSize),
    }
    
    for i := 0; i < workers; i++ {
        go p.worker()
    }
    
    return p
}

func (p *WorkerPool) worker() {
    for job := range p.jobs {
        p.results <- job()
        p.wg.Done()
    }
}

func (p *WorkerPool) Submit(job Job) {
    p.wg.Add(1)
    p.jobs <- job
}

func (p *WorkerPool) Wait() {
    p.wg.Wait()
    close(p.results)
}

func (p *WorkerPool) Close() {
    close(p.jobs)
}

func (p *WorkerPool) Results() <-chan error {
    return p.results
}
```

### Implement Concurrent Map

```go theme={null}
const shardCount = 32

type ConcurrentMap struct {
    shards [shardCount]*mapShard
}

type mapShard struct {
    mu    sync.RWMutex
    items map[string]interface{}
}

func NewConcurrentMap() *ConcurrentMap {
    m := &ConcurrentMap{}
    for i := 0; i < shardCount; i++ {
        m.shards[i] = &mapShard{items: make(map[string]interface{})}
    }
    return m
}

func (m *ConcurrentMap) getShard(key string) *mapShard {
    hash := fnv32(key)
    return m.shards[hash%shardCount]
}

func (m *ConcurrentMap) Set(key string, value interface{}) {
    shard := m.getShard(key)
    shard.mu.Lock()
    shard.items[key] = value
    shard.mu.Unlock()
}

func (m *ConcurrentMap) Get(key string) (interface{}, bool) {
    shard := m.getShard(key)
    shard.mu.RLock()
    val, ok := shard.items[key]
    shard.mu.RUnlock()
    return val, ok
}

func (m *ConcurrentMap) Delete(key string) {
    shard := m.getShard(key)
    shard.mu.Lock()
    delete(shard.items, key)
    shard.mu.Unlock()
}

func fnv32(key string) uint32 {
    hash := uint32(2166136261)
    for i := 0; i < len(key); i++ {
        hash *= 16777619
        hash ^= uint32(key[i])
    }
    return hash
}
```

***

## System Design Topics

### Design a URL Shortener

```go theme={null}
type URLShortener struct {
    store    map[string]string  // shortCode -> longURL
    counter  atomic.Uint64
    mu       sync.RWMutex
}

func (s *URLShortener) Shorten(longURL string) string {
    id := s.counter.Add(1)
    shortCode := base62Encode(id)
    
    s.mu.Lock()
    s.store[shortCode] = longURL
    s.mu.Unlock()
    
    return shortCode
}

func (s *URLShortener) Resolve(shortCode string) (string, bool) {
    s.mu.RLock()
    defer s.mu.RUnlock()
    url, ok := s.store[shortCode]
    return url, ok
}

func base62Encode(num uint64) string {
    const alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    if num == 0 {
        return "0"
    }
    
    var result []byte
    for num > 0 {
        result = append([]byte{alphabet[num%62]}, result...)
        num /= 62
    }
    return string(result)
}
```

**Scalability considerations:**

* Distributed ID generation (Snowflake, UUID)
* Database sharding by short code prefix
* Caching (Redis) for hot URLs
* Rate limiting per user/IP

### Design a Pub/Sub System

```go theme={null}
type Message struct {
    Topic   string
    Payload []byte
}

type Broker struct {
    mu          sync.RWMutex
    subscribers map[string][]chan Message
}

func NewBroker() *Broker {
    return &Broker{
        subscribers: make(map[string][]chan Message),
    }
}

func (b *Broker) Subscribe(topic string, bufSize int) <-chan Message {
    ch := make(chan Message, bufSize)
    
    b.mu.Lock()
    b.subscribers[topic] = append(b.subscribers[topic], ch)
    b.mu.Unlock()
    
    return ch
}

func (b *Broker) Publish(msg Message) {
    b.mu.RLock()
    subs := b.subscribers[msg.Topic]
    b.mu.RUnlock()
    
    for _, ch := range subs {
        select {
        case ch <- msg:
        default:
            // Channel full, drop or log
        }
    }
}

func (b *Broker) Unsubscribe(topic string, ch <-chan Message) {
    b.mu.Lock()
    defer b.mu.Unlock()
    
    subs := b.subscribers[topic]
    for i, sub := range subs {
        if sub == ch {
            b.subscribers[topic] = append(subs[:i], subs[i+1:]...)
            close(sub)
            break
        }
    }
}
```

***

## Performance Questions

<AccordionGroup>
  <Accordion title="How do you profile a Go application?">
    ```bash theme={null}
    # CPU profiling
    go test -cpuprofile=cpu.prof -bench=.
    go tool pprof cpu.prof

    # Memory profiling
    go test -memprofile=mem.prof -bench=.
    go tool pprof mem.prof

    # HTTP pprof
    import _ "net/http/pprof"
    go func() { http.ListenAndServe(":6060", nil) }()

    # Trace
    go test -trace=trace.out
    go tool trace trace.out
    ```
  </Accordion>

  <Accordion title="How do you reduce memory allocations?">
    * Pre-allocate slices: `make([]T, 0, expectedSize)`
    * Use `sync.Pool` for temporary objects
    * Avoid string concatenation in loops (use `strings.Builder`)
    * Reuse buffers
    * Use value types instead of pointers when appropriate
    * Align struct fields properly

    ```go theme={null}
    // Check allocations in benchmarks
    func BenchmarkExample(b *testing.B) {
        b.ReportAllocs()
        for i := 0; i < b.N; i++ {
            // ...
        }
    }
    ```
  </Accordion>

  <Accordion title="What is escape analysis?">
    Escape analysis determines whether a variable can stay on the stack or must "escape" to the heap:

    ```go theme={null}
    // Stack allocation
    func stack() int {
        x := 42
        return x  // Value copied, x stays on stack
    }

    // Heap allocation (escapes)
    func heap() *int {
        x := 42
        return &x  // Pointer escapes, x allocated on heap
    }

    // Check escape decisions
    go build -gcflags="-m" ./...
    ```

    Stack is faster (no GC), heap is necessary for escaped values.
  </Accordion>
</AccordionGroup>

***

## Best Practices Checklist

### Code Quality

* [ ] Use `go fmt` and `go vet`
* [ ] Run `golangci-lint`
* [ ] Write table-driven tests
* [ ] Use meaningful variable names
* [ ] Keep functions small and focused
* [ ] Handle all errors
* [ ] Use interfaces for dependencies

### Concurrency

* [ ] Prefer channels for communication
* [ ] Always use context for cancellation
* [ ] Run race detector (`-race`)
* [ ] Close channels from sender side
* [ ] Use `sync.WaitGroup` for goroutine synchronization

### Performance

* [ ] Profile before optimizing
* [ ] Pre-allocate slices when size is known
* [ ] Use `sync.Pool` for frequent allocations
* [ ] Avoid defer in hot loops
* [ ] Use buffered I/O

### Production

* [ ] Structured logging
* [ ] Health checks
* [ ] Graceful shutdown
* [ ] Configuration management
* [ ] Metrics and monitoring
* [ ] Proper error handling with stack traces

***

## Quick Reference Card

```go theme={null}
// Slice operations
s := make([]int, 0, 10)  // len=0, cap=10
s = append(s, 1, 2, 3)   // Append elements
copy(dest, src)          // Copy slices

// Map operations
m := make(map[string]int)
m["key"] = 1
val, ok := m["key"]  // Check existence
delete(m, "key")     // Delete key

// Channel operations
ch := make(chan int)      // Unbuffered
ch := make(chan int, 10)  // Buffered
ch <- val                 // Send
val := <-ch              // Receive
close(ch)                // Close

// Concurrency primitives
var mu sync.Mutex
var rmu sync.RWMutex
var wg sync.WaitGroup
var once sync.Once
var pool sync.Pool

// Context
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
ctx, cancel := context.WithCancel(ctx)
ctx = context.WithValue(ctx, key, val)

// Error handling
if err != nil {
    return fmt.Errorf("operation failed: %w", err)
}
errors.Is(err, target)
errors.As(err, &target)
```

***

## Summary

Mastering Go for interviews requires:

1. **Strong fundamentals**: Types, interfaces, error handling
2. **Concurrency expertise**: Goroutines, channels, sync primitives
3. **Performance awareness**: Profiling, memory management
4. **Production experience**: Logging, config, deployment
5. **Coding practice**: LeetCode-style problems in Go
6. **System design**: Distributed systems patterns

Good luck with your interviews!

***

## Interview Deep-Dive

<AccordionGroup>
  <Accordion title="You are designing a concurrent URL shortener service in Go that needs to handle 100K requests per second. Walk me through your design decisions for the data structures, concurrency model, and potential bottlenecks.">
    **Strong Answer:**

    * The core data structure is a mapping from short codes to long URLs. At 100K RPS, a single mutex-protected map becomes a bottleneck due to lock contention. I would use a sharded concurrent map: 256 shards, each with its own `sync.RWMutex`. The shard is selected by hashing the short code. Reads use `RLock` (multiple concurrent readers), writes use `Lock`. This reduces contention by 256x.
    * For ID generation, I would use `atomic.Uint64.Add(1)` to generate sequential IDs without locks, then encode to base62 for the short code. At 100K RPS, a uint64 counter would not overflow for millions of years.
    * The concurrency model: the HTTP server handles requests with the standard net/http server (one goroutine per connection, multiplexed by the Go runtime). For writes (creating short URLs), the sharded map handles the concurrency. For reads (resolving short codes), the sharded map with RLock allows full concurrent reads.
    * Bottlenecks: at 100K RPS, the in-memory map is fast, but persistence becomes the bottleneck. I would write to a buffered channel and have a pool of database writer goroutines batch-insert to PostgreSQL asynchronously. For reads, I would put a Redis cache in front of the database, with the in-memory sharded map as an L1 cache.
    * Edge cases: URL validation (reject malformed URLs), rate limiting per IP to prevent abuse, TTL for short URLs (periodic cleanup goroutine), and handling the case where two concurrent requests try to shorten the same long URL (use the long URL as a secondary key to deduplicate).

    **Follow-up: How would you handle the transition from a single-node in-memory store to a distributed system with multiple instances?**

    With multiple instances, the in-memory map becomes a local cache, and the source of truth moves to a shared database (PostgreSQL with the short code as primary key) and a shared cache (Redis). ID generation must be distributed -- options include: Snowflake-style IDs (combining timestamp, instance ID, and sequence), a centralized counter service, or random UUIDs with collision detection. I would use Redis INCR for atomic distributed counters -- it is fast enough for 100K RPS and guarantees uniqueness. Each instance would still maintain an in-memory LRU cache for the hottest URLs to reduce Redis/database load. The cache invalidation strategy is simple: short URL mappings are immutable once created, so the cache never needs invalidation, only TTL-based expiry.
  </Accordion>

  <Accordion title="Implement a thread-safe LRU cache in Go. Explain your choice of data structures, how you handle concurrency, and what the time complexity is for each operation.">
    **Strong Answer:**

    * The standard LRU cache uses a doubly-linked list (for O(1) move-to-front and remove-from-back) combined with a hash map (for O(1) key lookup). On `Get`: look up the key in the map, if found, move the node to the front of the list and return the value. On `Put`: if the key exists, update the value and move to front. If it does not exist, create a new node at the front. If capacity is exceeded, remove the tail node (least recently used) and delete it from the map.
    * For thread safety, I would wrap the entire data structure with a `sync.RWMutex`. Reads (Get) use `RLock` and writes (Put) use `Lock`. However, since `Get` also modifies the list (move to front), a pure `RLock` is not sufficient for `Get` -- you need a `Lock` for both read and write operations. This means all operations are mutually exclusive.
    * To improve concurrency, you could shard the cache: 16 independent LRU caches, each with its own lock. The shard is selected by hashing the key. This allows 16 concurrent operations on different shards.
    * Time complexity: Get is O(1) (hash map lookup + linked list move). Put is O(1) (hash map insert + linked list insert + possible tail removal). Space complexity: O(capacity) for both the map and the list.
    * In production, I would consider using `sync.Map` for read-heavy workloads, or a purpose-built library like `github.com/hashicorp/golang-lru` which is battle-tested. For very high concurrency, a clock-based approximation (CLOCK or CLOCK-Pro) avoids the linked list manipulation on every access.

    **Follow-up: What race condition could occur if you used `sync.RWMutex` with `RLock` for `Get` and `Lock` for `Put`?**

    Since `Get` moves the accessed node to the front of the doubly-linked list, it is a write operation on the list structure even though it is logically a "read" on the cache. If two goroutines call `Get` concurrently with `RLock`, they both try to modify the linked list pointers simultaneously -- a data race. The list node's `prev` and `next` pointers would be corrupted, potentially creating a cycle or dangling pointer. The fix is to use `Lock` (exclusive) for `Get` as well, accepting that all operations are serialized per shard. An alternative is a lock-free design: use an atomic counter for access frequency instead of a linked list for recency, approximating LRU with LFU-like behavior (as some high-performance caches like Ristretto do).
  </Accordion>

  <Accordion title="A Go service has been running for 3 days and its memory usage has grown from 200MB to 2GB. The heap profile shows nothing unusual. What else could be causing this, and how do you investigate?">
    **Strong Answer:**

    * If the heap profile shows steady allocations but RSS keeps growing, there are several possibilities beyond heap leaks.
    * First, goroutine leak: check `runtime.NumGoroutine()`. Each goroutine consumes at least 2KB of stack, and stacks can grow. 100,000 leaked goroutines could consume 200MB+. Take a goroutine profile (`/debug/pprof/goroutine?debug=2`) and look for goroutines blocked on channel operations or sleeping forever.
    * Second, stack growth: goroutine stacks start at 2KB but grow dynamically (up to 1GB by default). If your code has deep call stacks or large stack-allocated arrays, a few thousand goroutines could use hundreds of MB. Check the goroutine profile for unusually deep stacks.
    * Third, memory fragmentation: the Go runtime requests memory from the OS in chunks and may not return it promptly. `HeapInuse` might be reasonable but `HeapSys` (memory obtained from OS) could be much higher due to fragmentation. Check `runtime.MemStats` -- if `HeapReleased` is low relative to `HeapSys`, the runtime is holding memory it does not actively use. Force return with `debug.FreeOSMemory()` as a test.
    * Fourth, cgo memory: if any dependency uses cgo, memory allocated by C code is invisible to the Go heap profiler. It does not show up in pprof but does show up in RSS. Use OS-level tools (pmap on Linux) to see where the memory is allocated.
    * Fifth, mmap'd files or shared memory: if the service memory-maps large files, these appear in RSS but not in the Go heap.
    * Investigation order: check goroutine count, check `MemStats`, compare `HeapInuse` vs `HeapSys` vs RSS, check for cgo usage, and finally use OS-level memory analysis tools.

    **Follow-up: How does Go's garbage collector handle long-lived objects differently from short-lived ones, and does Go have generational GC?**

    Go does NOT use generational garbage collection (unlike Java's JVM). Go's GC treats all objects equally -- there is no young generation or old generation. Every GC cycle scans all live objects. This means long-lived objects are not "promoted" to a less-frequently-scanned heap area; they are scanned on every cycle. The consequence is that Go's GC overhead scales with the number of live objects (pointers to scan), not just the allocation rate. If you have millions of long-lived pointers in memory (like a large in-memory cache), each GC cycle must trace through all of them. The mitigation strategies are: reduce the number of pointer-containing objects (use value types, use slices of values instead of slices of pointers), use off-heap storage for large caches (memory-mapped files, or store data as `[]byte` which the GC treats as a single opaque blob), or tune `GOGC` and `GOMEMLIMIT` to control GC frequency and memory budget.
  </Accordion>
</AccordionGroup>
