skip to content

questions

21

In a per-request scratch arena reset at the response boundary, how do allocation and release actually work?

level: middleimportance: must knowfreq 62%

answer

  1. one cursor, one direction
  2. no header beside each object
  3. release is not per object
  4. the boundary is the release point
  5. rewinding the cursor releases everything

basics

~20 s

Allocation aligns one cursor, hands back its old position and advances it past the object, storing no per-object metadata. Nothing is released individually: at the response boundary the cursor is rewound to the start, which releases everything in the region at once.

solid answer

~50 s

An arena is one contiguous region with a single cursor, often called a bump pointer, dividing memory already handed out from memory still free. To allocate, you round the cursor up to the required alignment, check the object still fits before the end of the region, return the rounded cursor and set it past the object. There is no search, no size class and no header beside the block, because the allocator will never be asked about a single object again. Release is not per object at all: at the phase boundary you `reset` the arena by rewinding the cursor to the start, which releases every object in it in constant time regardless of how many there were. The price is that nothing comes back early, and any pointer that survives the reset now addresses storage the arena will hand out again.

code

pseudocode · 10 lines
pseudocode
function arena_alloc(arena, size, alignment):
    p = round_up(arena.next, alignment)
    if p + size > arena.end:
        return grow_or_fail(arena, size)   # chain a block, or fail the phase
    arena.next = p + size
    return p                               # no header, no free list entry

function arena_reset(arena):
    arena.next = arena.start               # every object released at once
    # bytes are NOT cleared and pages are NOT returned here

go deeper

for a junior

Remember the shape: one region, one cursor that only moves forward, and a single reset that releases everything when the phase ends.

for a middle

Be able to walk the allocation path out loud — align, bounds-check, return, advance — and say why no per-object header exists and why that makes individual release impossible.

for a senior

Show that you treat the reset as a contract: nothing may outlive it, non-memory resources still need explicit cleanup, and the per-phase allocation total is a budget somebody owns.

for a principal

Frame it as moving release cost to a boundary the application already has, and be ready to say which workloads have no such boundary and therefore should not pay the design's obligations.

## What an arena is An **arena**, also called a **region**, is a contiguous block of memory that a program obtains once and then sub-allocates itself. Inside it sits a single cursor — the **bump pointer** — marking the boundary between the part of the region already handed out and the part still free. Every arena has an owner and a lifetime, and in the shape this question is about, the lifetime is a **phase**: one request, one rendered report, one parse, one frame. The defining property is not speed. It is that **the release decision is made once, for the whole region, by whoever owns the phase** — not object by object by whoever happened to allocate. ## The allocation path Allocating from a bump-pointer arena is three steps and no search: 1. Round the cursor up to the alignment the requested object needs. 2. Check that the rounded cursor plus the requested size still lies before the end of the region. 3. Return the rounded cursor as the address, and set the cursor to just past the new object. What is absent matters more than what is present. There is no free list to walk, no fitting decision, and no per-object header recording a size so that a later release can find the block. The allocator keeps **no per-object metadata at all**, because nothing will ever ask it about one object again. Two consequences follow: the fast path is a handful of instructions with no branch that depends on the heap's history, and objects allocated in sequence end up adjacent in memory, which a walk over them later reads out of cache in order. If step 2 fails, an arena has two honest policies: **chain another block** (the arena becomes a list of blocks, and contiguity now holds per block rather than across the whole arena), or **fail the phase** against a declared budget. Choosing deliberately between those two is part of adopting the design. ## The release path Release is the reset: the cursor goes back to the start of the region. Every object in it dies at the same instant, and the cost is constant — one store — no matter whether the phase allocated ten objects or ten million. That is the whole trade: the per-object release work that a general-purpose allocator does N times is replaced by one operation at a boundary the application already has. Three things a reset does **not** do, and each is a real source of bugs: - It does not **zero** the region. The bytes of the previous phase are still there until something writes over them. - It does not necessarily hand the memory back to the operating system. The usual behaviour is to keep the backing pages so the next phase reuses warm memory; returning pages is a separate, explicit decision. - It does not run any **per-object cleanup**. An object that owns something other than memory — an open handle, a lock, a registration in some table — must have that released explicitly. The arena reclaims bytes and nothing else. ## Against a general-purpose allocator | | Bump-pointer arena | General-purpose allocator | |---|---|---| | Allocate | Advance one aligned cursor | Find a block that fits the request | | Per-object metadata | None | A header or tag beside each block | | Release one object | Not possible | Supported, and expected | | Release everything | One cursor reset | One release per live object | | Peak footprint | Everything allocated in the phase | Roughly the live set | | Dominant risk | A pointer outliving the reset | Forgetting an individual release | ## What the design buys, and what it obliges It buys a fast, branch-light allocation path; constant-time bulk release; no per-object bookkeeping; good locality; and the disappearance of one whole bug class, the object nobody remembered to release. It obliges you to accept that **nothing is reclaimed early**. Memory that goes unused halfway through the phase is still held until the boundary. It obliges you to copy out, before the reset, anything that must outlive the phase — otherwise the caller keeps a pointer into storage the arena is about to hand out again, and reads there return whatever the next phase wrote. And it turns the per-phase allocation total into a number somebody now owns, because it is the number that decides the process footprint. The design therefore fits work with a **clear boundary and a bounded budget**: build the whole report, write the response, reset. It fits badly where objects have individual, unpredictable lifetimes, or where the phase can run arbitrarily long.

  • What has to happen to an object that must outlive the phase the arena serves?
    It has to be copied into storage with a longer lifetime before the reset, and the caller must be handed the copy. Keeping the original pointer is unsafe: after the reset the arena hands that same storage out again, so later reads see the next phase's data rather than the object.
  • Does resetting an arena give the memory back to the operating system?
    Not by default. A reset rewinds the cursor and normally keeps the backing pages, precisely so the next phase allocates into warm memory with no system call. Returning pages is a separate, explicit action, and it costs the next phase the faults it avoided.

A whiteboard used for one meeting: you write wherever the last line ended and never erase a single word, then wipe the whole board when the meeting ends. Anything you still need has to be photographed before the wipe.

saying these in an interview costs you the question

  • Thinks an individual object inside an arena can be released on its own.
  • Assumes a reset zeroes the region, so recycled bytes are always clean.
  • Believes a reset also runs cleanup for handles and locks the objects held.
  • Says an arena removes the need for any memory budget on the phase.
  • Claims bump allocation makes the whole process free of fragmentation.
  • Keeps returning pointers into the arena to callers that outlive the phase.
open as a page

Which fragmentation does an allocator harness measure when it reports 100 MB requested but 128 MB occupied, and which does it miss?

level: middleimportance: must knowfreq 62%

basics

~10 s

That 28 MB gap is internal fragmentation: bytes lost rounding each request up to a whole block. It cannot see external fragmentation, the free-but-scattered space between blocks, because nobody occupies those bytes.

open as a page

When an allocator frees a block, how do boundary tags let it merge with both neighbouring free blocks in constant time?

level: middleimportance: must knowfreq 58%

basics

~20 s

Boundary tags repeat each block's size and free flag in a header and a footer, so a released block reaches both address neighbours by arithmetic and merges with whichever are free — no list scan.

open as a page

A heap allocator searching one free list can take the first block that fits or the smallest; what does each policy cost?

level: middleimportance: must knowfreq 66%

basics

~20 s

First fit stops at the first adequate block, so the search is short but early splits leave small remainders near the list head. Best fit scans the whole list for the tightest block, paying a full walk to leave a smaller remainder.

open as a page

How does a per-thread allocator cache make the common allocation path run with no synchronization at all?

level: middleimportance: must knowfreq 54%

basics

~20 s

A per-thread allocator cache gives each thread its own free list per size class. Allocation pops that private list's head, and since no other thread can reach it, the pop needs no lock — only refill and flush synchronize.

open as a page

In an allocator guarded by one shared lock, why does allocation throughput flatten as a worker pool grows from four threads to sixty-four?

level: middleimportance: must knowfreq 62%

basics

~20 s

Allocation is a short critical section, so one shared allocator lock serializes every thread through it. Past a few threads each extra worker mostly waits, and the handoff costs more than the work itself, so throughput saturates while cores idle.

open as a page

How does a fixed-cell object pool reclaim memory differently from an arena reset at a phase boundary?

level: middleimportance: should knowfreq 46%

basics

~20 s

A pool never reclaims in the usual sense: identical cells circulate between borrowers, returned one at a time and handed straight back out. An arena reclaims everything at once at a boundary and cannot take back a single object before it.

open as a page

A size-class allocator rounds every 3 KB request up to a 4 KB block; which fragmentation does that trade away, and what does it buy?

level: middleimportance: should knowfreq 48%

basics

~20 s

It trades unbounded gap waste for bounded rounding waste. Within one class every free block fits every request of that class, so unusable gaps cannot form there; the price is 1,024 idle bytes per 3 KB request, a quarter of each block.

open as a page

How do segregated free lists with size classes turn an allocation request from a list search into an index lookup?

level: middleimportance: should knowfreq 52%

basics

~20 s

The allocator keeps an array of free lists, one per size class. A request is mapped to a class index by arithmetic on its size, and the head of that list is popped — every block on it already fits, so nothing is searched.

open as a page

A per-request arena is reset on every response, yet the service's peak memory far exceeds its live data. Why?

level: seniorimportance: should knowfreq 41%

basics

~20 s

Because an arena holds everything allocated during the phase, not what is still in use. Intermediate scratch that died early is still occupying the region until the reset, and that total is multiplied by the number of requests in flight.

open as a page

An object pool hands back recycled response buffers, and a response occasionally carries bytes from an earlier request. Why?

level: seniorimportance: should knowfreq 50%

basics

~20 s

A recycled cell arrives holding whatever the previous borrower left in it. If the new borrower writes fewer bytes than the old one and the consumer reads the cell's capacity or a stale length, the untouched tail is the previous request's data.

open as a page

Why can an allocator's total free bytes overstate what it can actually hand out, and which second number corrects it?

level: seniorimportance: should knowfreq 45%

basics

~20 s

A request needs one contiguous run, but total free bytes sums runs that may be scattered. The largest contiguous free run is the corrective number: it, not the total, bounds the biggest single request that can be placed.

open as a page

One thread allocates buffers that a different thread frees — what does that pattern do to per-thread allocator caches?

level: seniorimportance: should knowfreq 44%

basics

~20 s

A free arriving on a thread that did not allocate the block breaks the one-owner assumption: blocks migrate from producers to consumers, draining one cache while inflating another, and the shared arena is back in the loop.

open as a page

A team proposes replacing general allocation with per-request arenas across a fleet of services. How would you decide whether that trade pays?

level: principalimportance: should knowfreq 33%

basics

~20 s

Test each service against four preconditions: a real phase boundary, allocation actually visible in its profile, a bounded per-phase allocation total, and no references escaping the reset. Adopt where all four hold, and price the higher peak footprint and the new dangling-pointer bug class honestly.

open as a page

How do you choose among size classes, neighbour coalescing, relocation and per-phase region reset when designing for a mixed-size, long-running workload?

level: principalimportance: should knowfreq 36%

basics

~20 s

Match each mitigation to the waste it can actually address, then pick by price. Size classes bound rounding waste, coalescing rebuilds runs from adjacent neighbours, relocation restores contiguity but must update references, and a region reset returns everything at a phase boundary.

open as a page

Your long-lived service must choose between one coalescing free list with best fit and segregated size classes; what evidence decides it?

level: principalimportance: should knowfreq 34%

basics

~20 s

A histogram of request sizes decides it: a few dominant sizes make classes an easy win, a long irregular tail does not. Then check the worst-case allocation latency you must meet, and the cost of owning the design.

open as a page

How would you decide how much memory per-thread allocator caches may hold across a fleet of services whose thread counts differ?

level: principalimportance: should knowfreq 34%

basics

~20 s

Cached memory scales as threads times size classes times blocks per class, so the decision is a curve: cache enough to keep the shared arena off the fast path, cap the per-thread total, and reclaim idle caches.

open as a page

A fixed-cell object pool that grew during a traffic spike still holds those cells hours later. What design choices control that retention?

level: seniorimportance: nice to knowfreq 27%

basics

~20 s

A pool that grows on demand and never trims is sized to its all-time peak, not to steady state, so a spike becomes a permanent footprint. The controls are a hard cap, idle eviction above a floor, shrinking on a low-water observation, or refusing to grow at all.

open as a page

Why can no allocator that never relocates a block promise to keep total memory within a constant factor of its live set?

level: seniorimportance: nice to knowfreq 22%

basics

~10 s

Because placement alone cannot defeat an adversarial request order. A classical worst-case result shows required memory grows with the live set times the logarithm of the largest-to-smallest request size ratio, for every fixed-placement policy.

open as a page

In an allocator that puts a size tag on every block, what does that metadata cost a workload of millions of small records?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

A per-block tag is a flat tax paid once per block, so it is worst where blocks are smallest: an eight-byte tag beside a 24-byte record is a third of the payload again, and a quarter of everything the allocator holds for it.

open as a page

Why does a per-thread allocator cache refill and flush in batches against the shared arena instead of one block at a time?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

Each trip to the shared arena costs a lock acquisition, so moving a batch amortizes it over many allocations. Separate low and high water marks stop a program sitting on the boundary from taking that lock every time.

open as a page