A service survives its load test, then restarts overnight with an out-of-memory termination. Its heap dashboard looks stable. Someone proposes lowering the garbage-collection target; someone else doubles the container limit. In an interview, the useful next move is to ask which memory measurement failed and which objects were still needed.

Memory questions let senior and staff candidates connect code behavior to operating constraints. This guide uses a Go service in a Linux container as a concrete example. The reasoning transfers to other runtimes, but their collectors and accounting differ. The scenarios, calculations, and rehearsal rubric below are illustrative preparation tools, not an employer's scoring system.

1. Define the memory boundary before diagnosing a leak

Start by identifying the failure: a process allocation error, a container OOM event, a node under pressure, or latency rising during reclamation. Ask for the runtime version, deployment configuration, traffic shape, recent release changes, and the exact metric behind “memory.” A screenshot without its accounting boundary is an observation to investigate.

Separate application objects, runtime-managed memory, process residency, and container accounting. Go's SetMemoryLimit documentation defines the runtime's soft-limit accounting as MemStats.Sys - MemStats.HeapReleased. It excludes memory managed outside Go, including C allocations and direct syscall.Mmap mappings. That value therefore cannot stand in for every byte charged to a container.

The Linux cgroup v2 documentation describes a wider boundary: memory.current includes memory charged to the cgroup and its descendants, with categories such as anonymous memory, page cache, and kernel memory exposed through memory.stat. Investigate the categories rather than subtracting two unrelated dashboards and declaring the difference a leak.

State a working contract: under a defined peak workload, the service completes accepted work within its latency target and stays within an operational memory budget. Include startup, large requests, canceled requests, and dependency slowdowns. Ask which work can be rejected or delayed. Those decisions determine whether a memory budget is enforceable.

2. Distinguish allocation churn from retained memory

A growing allocation counter does not, by itself, establish a leak. The runtime/metrics reference defines /gc/heap/allocs:bytes as cumulative heap allocation, while /gc/heap/live:bytes describes objects marked live by the previous collection. Track the allocation counter's change per second and compare live heap across completed collections. Also record the heap goal, GC CPU, goroutine count, request concurrency, and container usage.

Build competing hypotheses. If live heap returns to a similar level while allocation rate and GC CPU rise, investigate churn. If the post-collection baseline grows under comparable traffic, investigate retention. If runtime memory remains stable while container usage grows, inspect the broader accounting boundary. These patterns guide the next measurement; none identifies a responsible code path alone.

Choose a profile that can answer the question

The pprof package documentation distinguishes inuse_space, which emphasizes live allocated bytes, from alloc_space, which includes bytes allocated over the process lifetime. The heap profile reflects the most recently completed GC and is sampled. Compare profiles at equivalent workload phases and record collection timing. A large allocation site identifies where objects originated, not necessarily the owner that keeps them reachable.

In a practice answer, propose a baseline, a controlled burst, and a recovery interval. Write down what each hypothesis predicts after the burst ends. This makes a second profile informative: you are looking for a specific change in retained objects or allocation rate, rather than hoping a flame graph supplies a diagnosis.

3. Follow ownership through slices, queues, and goroutines

Imagine a parser extracts a short identifier from a large input and stores it in a cache. Go's slice internals article explains that a subslice still references its backing array. Retaining the small result can keep the entire array reachable. Copying just the required bytes can break that ownership relationship, at the cost of another allocation and copy.

Do not apply that fix indiscriminately. Ask how long the result lives, how large the source buffers are, and how often this path executes. Compare retained bytes and CPU before and after the change. A copy is useful when it shortens a costly lifetime; it may add no retention benefit when the result does not outlive the source, or the backing array must remain reachable for another reason.

Next, make cancellation part of the ownership story. The Go team's pipelines and cancellation guide shows how senders can remain blocked when downstream consumers stop reading. Their stacks can retain heap references, and goroutines must exit through program logic. Explain who cancels upstream work, which blocking operations observe cancellation, and how tests establish that workers finish.

Bound queues in bytes as well as item count when payload sizes vary. Define ownership while a request waits, while a worker processes it, and after a client disconnects. An interview follow-up might add slow downstream storage: explain whether producers block, reject work, spill to durable storage, or keep accumulating buffers. Each choice needs an explicit capacity and cleanup policy.

4. Calculate a budget before tuning the collector

Use a small model to expose assumptions. In an illustrative calculation, suppose a container has a 1,024 MiB limit. Reserve 224 MiB for measured non-Go usage, other charges, and uncertainty, leaving an initial 800 MiB runtime budget. If the workload needs 400 MiB of retained data, 100 MiB of other runtime memory, and room for bursts, explain how those components fit before choosing a setting. The reserve is a hypothetical planning input, not a universal percentage.

Go's garbage collector guide models the heap target as live heap plus the live heap and GC roots multiplied by GOGC / 100. Raising GOGC generally trades more heap headroom for less frequent collection; lowering it spends more collection work to reduce that headroom. A target is not a hard ceiling. Changing it does not make reachable data collectible.

For a second illustrative calculation, 300 MiB of live heap and 20 MiB of roots with GOGC set to 100 give a modeled heap target of 620 MiB. That is a heap estimate, not total process or container consumption. A separate runtime memory limit can constrain the target further. State the assumptions aloud so the interviewer can change them without invalidating the entire answer.

Set GOMEMLIMIT below the container boundary with measured room for excluded memory and spikes. The soft limit can be exceeded; it is not an OOM guarantee. The SetMemoryLimit API also warns that an infeasibly low limit can drive nearly continuous GC. If the required working set barely fits, reduce retained work, shed load, or increase capacity instead of treating collection frequency as free memory.

5. Explain what the container limit actually enforces

The Kubernetes resource-management documentation separates requests used for scheduling from limits enforced at runtime. Memory-limit enforcement is reactive and can result in an OOM kill. A configured memory request does not make application allocations bounded. Check the effective container and Pod configuration rather than assuming a deployment manifest excerpt describes every boundary.

Linux also distinguishes memory.high, which applies throttling and reclaim pressure, from memory.max, the hard usage boundary that can lead to cgroup OOM handling when reclaim cannot satisfy demand. Consult the cgroup documentation and inspect memory.events counters alongside latency. A service can become slow under memory pressure before a termination appears. Avoid describing every slowdown as a stop-the-world GC pause.

Consider an interviewer changing the premise: the Go heap is stable but a native compression library's memory grows. Your response should change too. A lower Go soft limit might create more collection work without reclaiming native buffers. Identify the native allocation owner, capture appropriate evidence, and bound or release those buffers. Keep mitigation and root-cause investigation separate so a restart does not become the only explanation.

6. Prove an improvement without moving the failure elsewhere

Choose one change that follows from evidence: release an oversized backing array, stop canceled workers, bound outstanding bytes, or adjust an overly aggressive runtime setting. Predict both the benefit and the cost. Then replay representative payload sizes, concurrency, cancellation, and dependency delays. Compare completed work, errors, tail latency, retained memory, allocation rate, and CPU over the same interval.

Pooling is an optimization to test, not an automatic fix. The sync.Pool contract allows cached items to disappear at any time. Use it for disposable temporary objects, with correct behavior when the pool is empty. Propose excluding exceptionally large buffers from reuse and resetting state before handing an object to another request. Measure whether reduced allocation outweighs extra retention and complexity.

Instrumentation also needs a budget. Go's diagnostics guide explains that profiles can add overhead and interfere with one another. Start with a bounded capture on a representative replica, protect diagnostic access, and preserve the workload context. Describe a rollback condition before a tuning experiment: for example, unacceptable latency or CPU despite lower memory. A smaller graph is not success if users receive less useful work.

7. Rehearse a memory investigation that survives follow-ups

Use this illustrative practice rubric: explain the accounting boundary, form two competing hypotheses, choose distinguishing evidence, trace an ownership lifetime, calculate a budget, and define a reversible experiment. Mark each element as missing, asserted, or supported. This is a self-review device, not a validated predictor of hiring outcomes.

Have a practice partner introduce one change halfway through: traffic doubles, payloads become larger, a downstream dependency stalls, or native memory replaces heap growth. Update the budget and expected evidence before proposing a fix. Record a short explanation of what you still do not know and the observation that would resolve it. That habit makes an answer adaptable instead of a memorized list of runtime flags.

For preparation, use Interview Copilot to practice explaining your investigation, then review whether each claim connects to a measurement or an explicit assumption. Keep the exercise focused on reasoning before an assessed interview, with enough detail to defend why your next experiment would change the decision.