Memory Interview Q&A — Developer Level
Memory Interview Questions & Answers
Senior developer interview questions on memory management — with good and great answers.
Q1: Difference between stack and heap?
Good answer: Stack is automatic (allocated on function entry, freed on return). Heap is dynamic (malloc/new, freed manually or by GC). Stack is faster.
Great answer adds: The real performance difference is cache locality. Stack data is contiguous → stays in L1 cache (~1ns). Heap allocations scatter across memory → cache misses (~100ns). Also, each thread owns its stack but shares the heap — requiring synchronization.
Q2: What happens when you call malloc()?
Good answer: Searches free list for a block. If not found, asks OS via brk()/mmap().
Great answer adds: Modern allocators (jemalloc, tcmalloc) use per-thread caches and size classes for O(1) allocation. free() doesn't always return memory to OS — keeps it in free list. This is why RSS stays high after freeing.
Q3: What is a stack overflow?
Good answer: Stack exceeds its limit (~1-8MB). Usually from deep/infinite recursion.
Great answer adds:
void recursive(int n) {
int buffer[1024]; // 4KB per frame
recursive(n + 1); // never returns!
}
// 2048 calls × 4KB = 8MB → overflow
// Also: stack overflow is exploitable (buffer overflow attacks)
// Protections: stack canaries, ASLR, NX bitQ4: What is a memory leak?
Good answer: Heap memory allocated but never freed, with no pointer referencing it.
Great answer adds:
void leak() {
char *buf = malloc(1024);
buf = malloc(2048); // ← first 1024 leaked!
free(buf); // only frees 2048
}
// In GC languages: "leaks" are unintentional references
// (growing list, event listeners, closures capturing vars)
// Tools: Valgrind, ASan, pprof (Go), heap dumps (Java)Q5: What is virtual memory?
Good answer: Each process gets its own address space. OS+MMU translates virtual → physical addresses. Provides isolation and overcommit.
Great answer adds: Three benefits: (1) isolation, (2) simplification (linker doesn't need physical layout), (3) overcommit. Also enables copy-on-write: fork() shares pages until write — then only that page copies. This is how Rails Puma workers share memory.
Q6: Is a page fault always bad?
Good answer: No. Minor faults (first touch of allocated memory) are normal and cheap (~1μs). Major faults (page swapped to disk) are expensive (~10ms).
Great answer adds:
char *buf = malloc(1GB); // no physical RAM used yet!
buf[0] = 'x'; // minor fault → maps 4KB page
// Only 4KB used, not 1GB! (overcommit)
// This is why malloc(1TB) succeeds on 16GB machine
// — as long as you don't touch it allQ7: Explain cache locality
Good answer: Accessing nearby memory addresses. CPU loads 64-byte cache lines, so nearby accesses are free.
Great answer adds:
// Same algorithm, same data, 30× speed difference:
for (i=0; i<N; i++) sum += array[i]; // ~10ms (sequential)
for (i=0; i<N; i++) sum += array[random[i]]; // ~300ms (random)
// This is why:
// - Arrays beat linked lists for iteration
// - Column-major vs row-major matters 10×
// - Game engines use Struct-of-Arrays
// - Databases use columnar storageQ8: What is false sharing?
Good answer: Two threads on different cores modify different variables on the same cache line. The line bounces between cores — 10-50× slowdown.
Great answer adds:
// Bad: both counters on same 64-byte cache line
var counters [2]int64 // 16 bytes, one cache line
// Thread 1: counters[0]++ → invalidates Core 2's line
// Thread 2: counters[1]++ → invalidates Core 1's line
// Fix: padding
type PaddedCounter struct {
value int64
_pad [56]byte // force separate cache lines
}
// Java: @Contended annotation
// Linux kernel: ____cacheline_alignedQ9: Process vs thread — memory perspective?
Good answer: Processes have separate address spaces. Threads share heap/globals but have own stacks.
Great answer adds: Thread creation is cheaper (~μs vs ~ms) because no page table copy. Thread bugs are easier (shared memory = race conditions). Process crash is isolated; thread crash kills all sibling threads. Go chose shared-memory + channels; Erlang chose isolated processes + message passing.
Q10: How does Go's escape analysis work?
// Stack (doesn't escape):
func sum() int {
x := 42 // lives and dies here
return x // value copied
}
// Heap (escapes):
func newUser() *User {
u := User{Name: "Alice"} // must survive return
return &u // pointer escapes → heap
}
// Check: go build -gcflags="-m"
// Stack alloc = free (no GC pressure)
// Hot path optimization: avoid escape = eliminate GC pausesQ11: Concurrency vs parallelism — memory perspective?
Parallelism causes:
- Visibility: Core 0 writes x=5, Core 1 still sees x=0 (needs memory barrier)
- Atomicity: 64-bit write might not be atomic on 32-bit CPU
- Ordering: CPU reorders reads/writes (memory model)
- Need: volatile (Java), atomic (Go/C++), Mutex, sync primitives
Q12: When would you use mmap?
Traditional: read(fd, buf, size) → 2 copies (kernel→user)
mmap: ptr = mmap(file) → 0 copies (zero-copy I/O)
Use cases:
- Database engines (SQLite, LMDB)
- Reading huge files without loading entirely
- Shared memory between processes
- JIT compilers (mmap + PROT_EXEC)