2Brute2Best
πŸ”₯0
M6.6

Implement a Fixed-Block Memory Pool / Custom Arena Allocator

Hard Pro OnlyEmbedded Free-ListO(1) Allocate & DeallocateZero Heap FragmentationPlacement new Object Pool
⚑ 15-Second Mental ModelM6.6 · Instant Intuition
Single-Read Mastery
The Real-World Physical Analogy (Core Intuition)

In modern C++, we never guess what the compiler does. We look directly at byte offsets, CPU registers, and object ownership lifetimes.

Rule to Remember: Make illegal states impossible to compile or run.
3 Golden Facts to Remember
Fact #1
What is the difference between an Arena Allocator (Bump/Linear Allocator) and a Free-List Object Pool?

A Bump/Arena Allocator simply advances an offset pointer (ptr += size) on each allocation and cannot deallocate individual objects; it frees all allocated memory at once when the entire arena scope is reset (ideal for per-frame or per-request scratch buffers). A Free-List Object Pool supports individual O(1) allocations AND deallocations in arbitrary order by recycling freed slots via an embedded linked list.

Fact #2
Why is an embedded free-list memory pool thread-unsafe by default, and how can it be made thread-safe with minimal overhead?

Modifying freeHead_ simultaneously from multiple threads causes data races and corrupted free-lists. To make it thread-safe:

  1. Lock-Free CAS Loop: Use std::atomic<Slot*> freeHead_ with compare_exchange_weak (note: beware of the ABA problem, which requires tagged pointers / 128-bit CAS).
  2. Thread-Local Pools (thread_local): Give each worker thread its own private FixedObjectPool. This achieves $100%$ zero lock contention and maximum cache locality without atomics.
Fact #3
What happens if a class allocated in a memory pool throws an exception inside its constructor?

In placement new (mem) T(...), if T's constructor throws, C++ guarantees that the constructor does not complete and stack unwinding begins. However, because placement new was used, the compiler does NOT automatically deallocate mem! To prevent memory leaks, you must wrap placement new in a try/catch block that catches the exception, calls deallocate(mem), and re-throws.

01

Interviewer's lens

What separates a strong C++ candidate from a textbook answer

Memory pools and arena allocators are among the most heavily tested systems programming topics in High-Frequency Trading (HFT), game engine development, and real-time audio systems. Interviewers ask candidates to implement an object pool from scratch because it reveals whether you understand hardware cache latency, memory alignment, placement new, and zero-cost data structures. Weak candidates propose a std::vector<bool> or an external array of pointers to track allocated slotsβ€”wasting memory and adding O(N) search overhead. Senior candidates immediately implement an embedded free-list (union Slot) that stores the nextFree pointer directly inside the unused payload bytes of free slots, achieving O(1) allocation and deallocation in 2 CPU instructions with strictly 0 bytes of metadata overhead.

02

Under the hood & memory layout

Hardware & memory representation

General-purpose heap allocators (malloc/new) are non-deterministic: they search complex boundary-tag free-lists (first-fit/best-fit), acquire global arena mutexes across threads, cause heap fragmentation, and trigger OS page faults via brk/mmap. A Fixed-Block Memory Pool pre-allocates a contiguous block of N slots upfront. The key architectural breakthrough is the Embedded Free-List: when a slot is actively holding a live object T, its bytes contain the fields of T; when the object is destroyed, those exact same bytes are repurposed to hold a single 8-byte pointer Slot* nextFree linking to the next available free slot. Because an object is either alive OR free (never both simultaneously), the memory overhead for tracking free blocks is identically 0 bytes. Allocation pops freeHead_ in 2 instructions; deallocation pushes the returned address onto freeHead_ in 2 instructions. Because the free-list operates in LIFO (Last-In, First-Out) order, the most recently freed slot is recycled first, guaranteeing that the slot's memory is already warm in the CPU's L1 data cache.

Contiguous Slot Buffer (N * sizeof(Slot))

A single contiguous block of aligned memory. Eliminates OS heap fragmentation and keeps all objects packed within sequential hardware memory pages.

The Embedded Free-List union (0B Metadata)

Defined as union Slot { alignas(T) std::byte storage[sizeof(T)]; Slot* nextFree; }. When active, storage holds T. When inactive, nextFree overlaps storage[0..7].

Placement new & Explicit ~T() Separation

Decouples raw memory allocation from object initialization. allocate() supplies the uninitialized byte address; new (mem) T(...) runs the constructor in-place. destroy() calls ptr->~T() without releasing memory back to the OS.

LIFO Cache-Line Reuse (L1 Warmth)

Freeing an object pushes it to freeHead_. The subsequent create() immediately pops that same slot, reusing the cache line while still hot in L1 cache (~1ns access latency).

03

Core concepts & syntax traps

01

Why General-Purpose new / delete Is Prohibited in Low-Latency Loops

Standard new and delete delegate to the OS allocator (such as ptmalloc, jemalloc, or tcmalloc). In high-frequency trading (HFT) and game tick loops, general-purpose heap allocation is strictly forbidden for three fundamental reasons:

  1. Unbounded Latency: Allocators maintain free-lists sorted by size classes. When fragmentation occurs, searching for a suitable block takes unpredictable time (O(K) free-list walks).
  2. Multithreaded Lock Contention: Allocators share arenas across threads. Under high concurrency, threads stall waiting for internal allocator mutex locks.
  3. OS System Calls & Page Faults: If heap arenas run dry, the allocator issues mmap or brk syscalls, dropping into kernel space and incurring virtual memory page faults.

A custom memory pool eliminates all three bottlenecks by pre-allocating memory up front, performing zero syscalls and zero locks on the hot path.

C++23 snippet
// PROHIBITED in HFT order routing hot path:
Order* o = new Order(id, price, qty); // Heap lock + non-deterministic latency!

// REQUIRED: O(1) deterministic pool allocation
Order* o = pool.create(id, price, qty); // 3 CPU instructions, 0 locks
Interview takeaway:

Pre-allocate memory pools at startup to ensure zero locks, zero page faults, and deterministic O(1) latency on hot paths.

02

The Embedded Free-List: Zero-Byte Metadata Overhead via union

How do we maintain a linked list of free slots without wasting 8 extra bytes per slot for a next pointer? By utilizing a union!

When a slot is allocated to the user, the memory contains the live payload of T. When the user destroys the object, that slot is empty. We cast the newly freed memory to Slot* and write the address of the current freeHead_ into its first 8 bytes! Because a slot is never active and free at the same time, the nextFree pointer requires strictly 0 bytes of auxiliary storage.

C++23 snippet
union Slot {
    // Ensure slot is at least as large as a pointer and aligned to T
    alignas(T) alignas(Slot*) std::byte storage[sizeof(T) >= sizeof(Slot*) ? sizeof(T) : sizeof(Slot*)];
    Slot* nextFree;
};

// Allocate: pop freeHead_
Slot* slot = freeHead_;
freeHead_ = freeHead_->nextFree;

// Deallocate: push onto freeHead_
slot->nextFree = freeHead_;
freeHead_ = slot;
Interview takeaway:

An embedded free-list reuses the dead payload memory of free slots to store next pointers with 0 bytes overhead.

03

Separating Memory Allocation from Construction: Placement new & Explicit ~T()

In standard C++, new T(...) performs two distinct operations: (1) calls operator new to allocate raw bytes, and (2) invokes T's constructor. Conversely, delete ptr calls T's destructor and then invokes operator delete to return bytes to the OS.

In a memory pool, we must decouple these two steps:

  • Construction: void* mem = pool.allocate(); T* obj = new (mem) T(args...); uses placement new (#include <new>) to construct T directly inside pre-allocated storage without invoking the OS allocator.
  • Destruction: We call the destructor explicitly via ptr->~T(), and then return the raw memory slot back to the pool via pool.deallocate(ptr). Never call delete ptr on pool memory!
C++23 snippet
template <typename... Args>
T* create(Args&&... args) {
    void* mem = allocate();
    if (!mem) return nullptr;
    return new (mem) T(std::forward<Args>(args)...); // In-place construction
}

void destroy(T* ptr) noexcept {
    if (!ptr) return;
    ptr->~T();                                        // Explicit destructor
    deallocate(static_cast<void*>(ptr));             // Return slot to free-list
}
Interview takeaway:

Always separate memory management from object lifetime using placement new and explicit destructor calls (~T()).

04

LIFO Allocation Order & Hardware L1 Cache Warmth

Because deallocate() pushes freed slots onto the front of freeHead_ and allocate() pops from the front, the embedded free-list naturally behaves as a LIFO (Last-In, First-Out) stack.

This provides a massive hardware performance advantage: when an object is destroyed and another object is immediately allocated, the pool reuses the exact same memory address that was just written to. That cache line is already resident in the CPU's L1 data cache, avoiding cache misses and memory bus traffic.

C++23 snippet
void* addr1 = pool.allocate();
pool.deallocate(addr1);
void* addr2 = pool.allocate();
// addr1 == addr2 is GUARANTEED! (LIFO stack reuse)
// The CPU accesses hot L1 cache instead of cold main RAM.
Interview takeaway:

LIFO free-list recycling keeps memory allocations warm in CPU L1 cache, drastically reducing cache misses.

Brute2Best Pro Content

Unlock Live Implementation & Interview Scenarios

The C++23 production code, live coding steps, scenario twist solutions, and common UB traps are part of Brute2Best Pro. Module 1 is completely free.

All 440 visual dry runs & algorithms
Interactive step-by-step playback with pointer & array state transitions
52 comprehensive LLD chapters
Interactive UML diagrams, real-world stories (Food Delivery, Trading, RPG), and practice solutions
40 C++ basics labs & hardware memory models
Vtables, byte layouts, assembly insights, and multi-threading race conditions
Production-ready C++23 source code
Modern idioms, clean contracts, time/space complexity derivations, and interview takeaways

First anchor question per pattern, Part 1 LLD & Module 1 C++ basics are 100% Free forever.

Next In Your Mastery Journey

Apply C++ Internals to LLD & Algorithmic Patterns

Full-stack systems engineering