This question separates people who think allocation is slow because it's "going to the OS" from people who know the actual fast path: a pointer bump into a thread-local heap region. The interviewer wants the fast path, the slow path, and when the fast path stops being free.
The mental model: allocation is two different operations. The fast path is giving you the next contiguous block of memory out of an already-mapped, already-zeroed region. The slow path is acquiring more memory when the region runs out.
Fast path walkthrough. In a modern managed runtime or allocator, each thread owns a thread-local allocation buffer (TLAB in the JVM, bump pointer in .NET, tcache in glibc). An allocation is: check current + size <= limit, if so, take current, bump current by the size rounded to the alignment (8 or 16 bytes), optionally zero the memory (C# does; Java relies on zeroed pages from the OS). No lock, no syscall, ~10–20 instructions. For small objects this is faster than the linked-list walk a textbook malloc implies.
Slow path walkthrough. When the TLAB is exhausted, the thread requests a new region from the runtime's global heap. That may trigger a GC (to reclaim dead objects before growing), or an OS-level call: mmap/brk for native allocators, which reserves address space and lazily maps physical pages on first touch — that first-touch page fault is where the real cost is. If the heap is out of the reserved region, the allocator may ask the OS for more, and the OS may hand back a region that requires TLB entries, page table work, and zeroing.
Tradeoffs and edge cases worth naming:
- Allocation is not deallocation. In a GC runtime, freeing is deferred to collection — allocation is cheap precisely because cleanup is batched.
- Size classes and freelists: large allocations (usually > a threshold like 128 KB) skip the TLAB and get mmap'd directly, so their lifespan is page-aligned.
- Locality: bump allocation means sequentially allocated objects are adjacent in memory — cache-friendly, which is why object pools are less necessary in modern runtimes.
- Zeroing costs: allocations after a GC compaction may need explicit zeroing, and that shows up in traces as increased latency.
A strong closing: "For the common case, allocation is a pointer bump in a thread-local region; the expensive parts — syscalls, zeroing pages, GC — only happen when that region runs out."