A memory pool pre-allocates a large block and serves fixed-size slots in O(1) time
Heap fragmentation disappears because all allocations are same size from a contiguous region
Pool allocators eliminate per-allocation lock contention using thread-local caches
Typical allocation latency: ~10-20ns vs 100-300ns for malloc (varies by implementation)
Production failure: pool exhaustion causes silent crashes when objects outlive pool lifetime
Biggest mistake: using pool for variable-size objects, causing internal fragmentation and wasted space
Plain-English First
Imagine you run a parking lot. Instead of letting cars park anywhere they want — creating gaps and making it hard to fit new ones — you pre-paint numbered spaces before the day starts. Every car goes into a pre-assigned slot instantly, no searching, no gaps. A memory pool allocator does exactly this for your program's RAM: it grabs a big chunk of memory upfront, slices it into fixed-size slots, and hands them out in microseconds — no rummaging through the heap, no fragmentation.
In a game engine rendering 120 frames per second, a trading system processing 10 million orders a minute, or an embedded firmware loop that must respond in under 50 microseconds, one thing will kill you faster than a logic bug: unpredictable memory allocation latency. The default new and deleteoperators are general-purpose tools — they handle any size, any time, with synchronization locks baked in. That generality has a cost, and in performance-critical C++ that cost is often unacceptable.
The standard heap allocator, whether it's glibc's ptmalloc, jemalloc, or TCMalloc, must maintain bookkeeping metadata, walk free-lists of variable sizes, coalesce adjacent freed blocks, and acquire locks in multithreaded contexts. Every call to new can trigger a system call, invalidate cache lines, and introduce allocations that are milliseconds apart in memory yet logically adjacent — fragmenting your address space until reallocation itself becomes a bottleneck. Memory pool allocators solve this by inverting the model: instead of asking the OS for memory on demand, you reserve a large contiguous arena upfront and serve allocations from it yourself, with full knowledge of your objects' sizes and lifetimes.
By the end of this article you'll understand how pool allocators work at the bit level, how to implement a thread-safe fixed-size pool and a more flexible slab-style allocator from scratch, how to plug them into STL containers via a custom allocator, and exactly when the tradeoffs make sense — and when they don't. You'll also leave with the mental model interviewers are probing for when they ask about custom allocators in systems programming interviews.
What is a Memory Pool Allocator?
A memory pool allocator (or fixed-size allocator) pre-allocates a large contiguous block of memory and divides it into slots of equal size. When you request an allocation, it returns a pointer to the next free slot — no searching, no coalescing. When you free, it marks the slot as available, often by pushing it onto a free list.
You'll find pool allocators in game engines, trading systems, embedded firmware, and custom STL allocators. They shine when you allocate many objects of the same type (e.g., particles, network packets, database rows) and you control their lifetimes tightly.
The key difference from general-purpose allocators is that pools don't handle arbitrary sizes. That's a feature, not a bug — by giving up generality, you gain speed and predictability.
io/thecodeforge/pool_allocator.hCPP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// TheCodeForge — Fixed-size pool allocator
#include <cstddef>
#include <cassert>
#include <new>
namespace io::thecodeforge {
classFixedPool {
structSlot {
union {
Slot* next; // free list pointer
char data[1]; // placeholder, actual size at runtime
};
};
Slot* pool_start_;
Slot* free_head_;
size_t slot_size_;
size_t slot_count_;
public:
FixedPool(size_t slot_size, size_t slot_count)
: slot_size_(slot_size), slot_count_(slot_count) {
if (slot_size < sizeof(Slot*))
slot_size_ = sizeof(Slot*); // ensure we can store free ptr
pool_start_ = static_cast<Slot*>(::operatornew(slot_size_ * slot_count_));
free_head_ = pool_start_;
for (size_t i = 0; i < slot_count_ - 1; ++i) {
Slot* current = reinterpret_cast<Slot*>(
reinterpret_cast<char*>(pool_start_) + i * slot_size_);
current->next = reinterpret_cast<Slot*>(
reinterpret_cast<char*>(pool_start_) + (i + 1) * slot_size_);
}
// last slot points to nullSlot* last = reinterpret_cast<Slot*>(
reinterpret_cast<char*>(pool_start_) + (slot_count_ - 1) * slot_size_);
last->next = nullptr;
}
void* allocate() {
if (!free_head_) throw std::bad_alloc();
Slot* slot = free_head_;
free_head_ = slot->next;
return slot;
}
voiddeallocate(void* ptr) {
Slot* slot = static_cast<Slot*>(ptr);
slot->next = free_head_;
free_head_ = slot;
}
~FixedPool() {
::operatordelete(pool_start_);
}
};
} // namespace
Think of It Like a Checkout Counter
Pre-allocated memory = stack of trays
free_head_ = pointer to the next tray
allocate = pop the tray (O(1))
deallocate = push it back (O(1))
Production Insight
A simple free-list pool without bounds checking is the most common source of heap corruption in game engines.
Double-free is invisible until a completely unrelated object gets corrupted.
Rule: always fill freed slots with a poison pattern and check free-list membership before every free.
Key Takeaway
Pools turn allocation into a pointer swap — O(1), deterministic, no locks per allocation.
The cost is wasted memory if slots are larger than needed (internal fragmentation).
Choose pool only when object size and lifetime are well understood.
When to Use a Fixed-Size Pool
IfAll allocations are the same size (e.g., particles, messages, db connections)
→
UseFixed-size pool — simple, fast, no fragmentation.
IfAllocations vary in size but only a few distinct sizes
→
UseSlab allocator (multiple pools, each for a size class).
IfLifetimes are nested or stack-like (LIFO)
→
UseArena/region allocator — even simpler than a pool.
IfYou need to free individual objects in any order
→
UsePool allocator with free list is appropriate.
Slab Allocator: Handling Multiple Sizes
A slab allocator maintains several fixed-size pools (slabs) for different size classes. When you request 24 bytes, it returns a chunk from the 32-byte slab. This trades external fragmentation for some internal fragmentation per slab, but still avoids the overhead of walking free lists of variable sizes.
The Linux slab allocator popularised this approach. It also caches recently freed objects in a per-CPU cache to avoid locking entirely.
Implementing a simple slab allocator means creating an array of FixedPool objects, each with a size from a predefined size table (usually powers of two plus some mid-range entries: 16, 32, 64, 128, ...). The allocator rounds up the requested size to the next slab size.
In production, slab allocators work well for networking stacks and kernel-level memory management.
Pure power-of-two slabs waste up to 50% of memory on average. Real slab allocators add mid-size entries to keep internal fragmentation under ~12%. For example, jemalloc uses size classes like 8, 16, 32, 48, 64, 80, 96, 112, 128, ...
Production Insight
Slab allocators complicate deallocation because you need to know the original size to find the right slab.
If you lose that information, you either store it (metadata overhead) or fallback to a full scan.
Rule: always store the slab index or size class inside the allocation header — 4 extra bytes is worth it.
Key Takeaway
Slab allocators combine the speed of pools with flexibility for multiple sizes.
You trade some internal fragmentation for deterministic allocation and no general-purpose locking.
Know your object size distribution before choosing slab sizes — garbage in, garbage out.
Choosing Between Pool and Slab
IfOnly one object type exists in the system
→
UseSingle pool — no slab machinery needed.
If2-10 distinct sizes, with predictable usage patterns
→
UseSlab with size classes. Pick classes that match real object sizes.
IfMany different sizes, all small (< 1KB)
→
UseSlab allocator (often integrated into malloc replacements like jemalloc).
IfLarge objects ( > 1KB) with sporadic allocations
→
UseDon't slab — let the general-purpose allocator handle them. Slabs waste memory on large objects.
Thread-Safety Considerations
The simple pool above uses a single free list protected by nothing. That's safe only in a single-threaded context. In multi-threaded environments, you can't share the free_head_ pointer without protection — threads will race, corrupt the list, and crash.
Three common strategies: 1. Global mutex around allocate/deallocate — simple but kills performance under contention. 2. Thread-local pools — each thread has its own pool, no sharing. Objects must be freed on the same thread that allocated them, or you need a transfer mechanism. 3. Lock-free free list — use atomic compare-and-swap (CAS) to manipulate the head pointer. This is the fastest option for moderate contention.
Let's implement a lock‑free pool using std::atomic<Slot*>.
The lock-free pool above has an ABA problem: if a slot is freed and then immediately allocated again, the CAS comparison sees the same pointer value but the list state has changed. In practice, for pools with small numbers of objects, ABA is rare. To fix it properly, use tagged pointers or hazard pointers.
Production Insight
Thread-local pools remove all lock overhead but force per-thread object ownership.
If thread A creates an object and thread B tries to free it, you'll corrupt thread A's pool.
Rule: if cross-thread frees happen, use a global pool with a CAS free list or a hand-off queue.
Key Takeaway
Pick thread safety strategy based on access patterns, not fear of locks.
Lock-free is not magic — it adds complexity and can still hurt under high contention.
Thread-local pools are the simplest scaler: no locks, no ABA, just memory pools per thread.
Choosing Thread Safety Strategy
IfSingle-threaded or only one thread touches the pool
→
UseNo sync needed — fastest path.
IfMultiple threads allocate and free on the same pool, moderate contention
→
UseLock-free pool with CAS (the implementation above).
IfHigh contention (>8 threads hammering the same pool)
→
UseThread-local pools per CPU or per thread. No sharing at all.
IfObjects are frequently freed on a different thread than they were allocated
→
UseUse a global pool with lock-free list or a receive queue per thread.
Plugging Into the STL: Custom Allocators
The STL containers like std::vector and std::map accept custom allocators via the template parameter. This lets you make std::vector<MyParticle, PoolAllocator<MyParticle>> use your pool instead of new/delete.
A custom allocator must satisfy the Allocator concept: it needs allocate() and deallocate() methods, a rebind struct, and equality operators. The C++17 std::pmr::memory_resource provides a higher-level interface, but we'll show the classic approach.
Here's an adaptor that wraps our FixedPool into an STL allocator.
io/thecodeforge/pool_allocator_adaptor.hCPP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// TheCodeForge — STL-compatible pool allocator
#include <memory>
#include <cstddef>
#include "io/thecodeforge/fixed_pool.h"namespace io::thecodeforge {
template <typename T>
classPoolAllocator {
public:
using value_type = T;
PoolAllocator(FixedPool& pool) noexcept : pool_(&pool) {}
template <typename U>
PoolAllocator(constPoolAllocator<U>& other) noexcept
: pool_(other.pool_) {}
T* allocate(std::size_t n) {
if (n != 1) throw std::bad_alloc(); // pool is single-slot onlyreturnstatic_cast<T*>(pool_->allocate());
}
voiddeallocate(T* p, std::size_t n) noexcept {
if (n == 1) pool_->deallocate(p);
}
template <typename U>
struct rebind {
using other = PoolAllocator<U>;
};
// equality: must share the same poolfriendbooloperator==(constPoolAllocator& a, constPoolAllocator& b) {
return a.pool_ == b.pool_;
}
friendbooloperator!=(constPoolAllocator& a, constPoolAllocator& b) {
return a.pool_ != b.pool_;
}
private:
FixedPool* pool_;
// allow cross-type copytemplate <typename U> friendclassPoolAllocator;
};
} // namespace
Allocator Traps to Avoid
STL containers use rebind to allocate internal nodes (e.g., std::list nodes are different from value_type). Your allocator must support rebinding or the container will fall back to std::allocator. Also, std::vector may request n > 1 — you can't assume single-object allocation.
Production Insight
STL custom allocators are swapped at compile time, not runtime.
If your pool is exhausted, the container either throws bad_alloc or if exceptions disabled, undefined behavior.
Rule: never let a container exceed its pool — use reserve() to pre-allocate.
Key Takeaway
Custom STL allocators are powerful but restrictive — they must meet the concept exactly.
If you just need fast alloc/free for a specific class, use the pool directly, not through STL.
std::pmr::memory_resource in C++17 is a cleaner abstraction for runtime polymorphic allocators.
When to Use STL Custom Allocators vs Direct Pool Usage
IfYou already use std::vector and want it to use your pool
→
UseWrite a PoolAllocator<T> adapter. But note: std::vector may request multiple items.
IfYou control the lifetimes explicitly (e.g., object pool pattern)
→
UseUse the pool directly — no need for STL layer.
IfYou need std::map or std::list on the pool
→
UseEnsure your allocator supports rebind — it will be used for nodes.
When Pool Allocators Lose: Trade-offs and Failure Modes
Pool allocators are not a silver bullet. They fail badly when object sizes vary widely, lifetimes are unpredictable, or memory must be returned to the OS. Here are the problems that catch teams in production:
Internal fragmentation — Fixed-size slots waste space if objects are smaller than the slot. A 64-byte pool for 16-byte objects wastes 75% of memory.
Memory blow-up under load — Pools never shrink. If an allocator reserves 1MB for 1024 objects of 1KB, a single peak usage event pins that 1MB forever.
No way to return memory to OS — The ::operator new inside the pool is freed only when the pool destructs. Long-lived pools are effectively memory leaks from the OS perspective.
Debugging difficulty — Use-after-free errors inside a pool are hard to detect because the slot memory still belongs to the pool and appears valid. Standard tools like ASan often can't catch intra-pool violations unless you poison freed slots.
For these reasons, pool allocators are best for hot paths where allocation counts are high and object sizes are uniform. For cold paths or unpredictable workloads, stick with general-purpose allocators.
io/thecodeforge/pool_failure_example.cppCPP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// TheCodeForge — demonstrating pool pitfalls
#include <iostream>
#include <memory>
#include <vector>
struct Particle { int x, y, vx, vy; }; // 16 bytes// Suppose pool is sized for 1000 particles at 64 bytes eachstructLargePool {
char data[64 * 1000];
size_t next = 0;
void* alloc() {
if (next >= 1000) returnnullptr;
return data + (next++ * 64);
}
// ... no free() - pool never releases memory
};
intmain() {
LargePool pool;
std::vector<Particle*> vec;
// worst-case: each particle uses only 16 bytes but pool wastes 48for (int i = 0; i < 1000; ++i)
vec.push_back(static_cast<Particle*>(pool.alloc()));
std::cout << "Wasted space: " << (64-16)*1000 << " bytes\n";
// pool memory never freed until program exitreturn0;
}
The Silent Memory Bloat
Pools appear to fix fragmentation, but they trade it for memory that never shrinks. In one production incident, a game's pool allocator held 2GB of 'free' memory that couldn't be given back. The fix: implement a shrink threshold that releases empty pages.
Production Insight
Pool memory is sticky — once allocated from the OS, it's not released until the pool dies.
Long-lived pools accumulate pages that are never used but counted as RSS.
Rule: if your pool has a steady-state overhead > 10% of total memory, consider an arena that can decommit or an allocator that returns pages.
Key Takeaway
Pools are hot-path tools — use them where allocation count and object size are known.
Never use a pool as a general memory manager for an entire application.
The biggest production mistake: running out of pool memory and having no fallback.
When NOT to Use a Pool Allocator
IfObjects have significantly different sizes (e.g., 8 bytes vs 8KB)
→
UseUse slab allocator or general-purpose allocator. A single pool wastes too much.
IfPeak allocation is much higher than steady state
→
UsePool holds peak memory forever. Use arena or fallback to malloc for spikes.
IfAllocations and frees are rare (once every few seconds)
→
UseGeneral-purpose allocator is fine — pool adds complexity without benefit.
IfYou need to detect memory leaks reliably
→
UseCustom pools hide leaks. Use malloc replacement with built-in leak detection.
● Production incidentPOST-MORTEMseverity: high
The Trading Engine That Crashed at Market Open
Symptom
Random segmentation faults and corrupted order fields appeared only during peak hours. The pool allocator returned seemingly valid pointers that pointed to already-freed memory.
Assumption
The team assumed the pool size was generous enough because it handled 99th percentile traffic. They never tested burst patterns.
Root cause
The pool had a fixed capacity of 100,000 slots. When that was exhausted, the custom pool returned a null pointer which the code didn't check. The null was later dereferenced, and the crash propagated. Worse, the pool's free list was corrupted by double-free from a poorly refactored object lifecycle.
Fix
Added a fallback to malloc when the pool is full, with a warning log. Also added a free-list validation (xor-linked list or magic numbers) to catch double-frees immediately. The pool size was adjusted to handle 3x the 99.9th percentile burst.
Key lesson
Never assume your pool will be large enough — always have a fallback allocator.
Check every allocation return — pools aren't guaranteed to succeed.
Instrument pool usage: log depletion warnings and monitor free-list integrity.
Production debug guideSymptom → Immediate Action — Real Commands and Checks5 entries
Symptom · 01
Random crashes with addresses inside pool region but objects appear corrupted
→
Fix
Check for double-free: enable free-list poisoning (fill freed slots with a known pattern like 0xDEADBEEF). Run with AddressSanitizer (ASan) to catch double-free and use-after-free.
Symptom · 02
Allocations start returning the same pointer repeatedly, then crash
→
Fix
Pool exhaustion. Add a counter to detect when the pool empty. Use fallback allocator. Check if objects are properly returned: log every allocate/free pair.
Symptom · 03
Performance drops suddenly, CPU cache misses spike
→
Fix
Your pool may be thrashing the cache because slots are too far apart. Verify the pool is aligned to cache line size. Use perf stat -e cache-misses to confirm.
Symptom · 04
Thread-safe pool deadlocks or livelocks under high contention
→
Fix
Check locking granularity. Use a per-thread cache (thread-local) instead of a single global lock. If you must share, try a lock-free stack with atomic CAS.
Symptom · 05
Memory usage grows indefinitely even though objects are freed
→
Fix
The pool never returns memory to the OS. If objects are freed but the pool isn't reused (e.g., arena growing unbounded), implement a size threshold to release empty chunks.
★ Pool Allocator Quick Debug Cheat SheetCommands and immediate actions for the most common pool allocator failures in production.
Double-free or use-after-free−
Immediate action
Recompile with -fsanitize=address and reproduce the crash.
Increase pool capacity to 3x peak usage and add a fallback to std::malloc with a warning.
Thread contention on shared pool lock+
Immediate action
Profile with perf top to see if pool_lock is the hottest function.
Commands
perf record -g -F 99 -- ./myapp && perf report
valgrind --tool=callgrind ./myapp # to see lock overhead
Fix now
Switch to per-thread pools (thread_local) or use a lock-free stack (e.g., Michael-Scott queue).
Memory Pool vs General-Purpose Allocator
Aspect
Pool Allocator
General-Purpose Allocator (malloc)
Allocation latency
~10-20 ns (pointer swap)
~100-300 ns (free list traversal)
Memory fragmentation
None external, some internal (slot waste)
External fragmentation over time
Memory return to OS
Only at pool destruction
Immediate on free of large blocks
Thread safety complexity
Must be explicitly built (lock-free/thread-local)
Built-in (but pay lock overhead every call)
Debugging ease
Harder (intra-pool corruption)
Easier (tools like ASan/Valgrind)
Best workload
Fixed-size, high frequency, hot path
Variable sizes, low frequency, cold path
Key takeaways
1
Memory pools replace general-purpose heap with O(1), deterministic allocation from a pre-reserved arena.
2
They eliminate external fragmentation but introduce internal fragmentation and sticky memory that never returns to the OS.
3
Thread safety must be designed in from the start
shared pools need mutex, lock-free, or thread-local strategy.
4
Custom STL allocators let you integrate pools into std::vector and std::map, but you must support rebind and handle n > 1.
5
Use pools only for hot paths with fixed-size objects and controlled lifetimes. For everything else, use malloc or a slab.
6
Always handle pool exhaustion with a fallback and monitor utilization
silent corruption is worse than a crash.
Common mistakes to avoid
5 patterns
×
Memorising syntax before understanding the concept
Symptom
You can write the pool code but can't explain when to use it. You reuse it everywhere, even for variable-size objects, causing internal fragmentation.
Fix
Understand the trade-offs (section 5). Before writing a pool, profile your allocation patterns. Only introduce a pool when you measure that malloc latency is a bottleneck.
×
Skipping practice and only reading theory
Symptom
You know the theory but your first implementation has bugs like double-free or pool corruption.
Fix
Implement the simple pool from section 1 by hand. Run it under valgrind. Then add thread safety. Then write a custom STL allocator. Each step reveals new pitfalls.
×
Using a fixed-size pool for variable-length objects
Symptom
You store strings or vectors inside pool-allocated objects. Each pool slot is fixed size, but the actual data spills to the heap anyway. You lose all benefits.
Fix
Only use pools for objects that contain all their data inline. If your type has dynamically allocated members, don't use a pool — or store them in a separate pool.
×
Forgetting to handle pool exhaustion
Symptom
Under peak load, the pool returns nullptr. If you don't check, you get a crash or data corruption that's hard to reproduce.
Fix
Always check the return of allocate(). In production, fall back to std::malloc with an error log. Monitor pool utilization and alert before exhaustion.
×
Sharing a non-thread-safe pool across threads
Symptom
Intermittent crashes under heavy concurrency. The free list gets corrupted because two threads read/write the head pointer simultaneously.
Fix
Either use thread-local pools, add a mutex, or implement a lock-free pool with CAS (as shown in section 3). Test with ThreadSanitizer.
INTERVIEW PREP · PRACTICE MODE
Interview Questions on This Topic
Q01SENIOR
Describe how a fixed-size pool allocator works internally. What are the ...
Q02SENIOR
How would you make a pool allocator thread-safe? Compare mutex, lock-fre...
Q03SENIOR
Explain the ABA problem in lock-free data structures and how it affects ...
Q04SENIOR
What is the difference between a pool allocator, an arena allocator, and...
Q01 of 04SENIOR
Describe how a fixed-size pool allocator works internally. What are the trade-offs compared to malloc?
ANSWER
A fixed-size pool pre-allocates a contiguous block of memory and divides it into equal-sized slots linked as a free list. Allocation pops from the head of the list (O(1)). Deallocation pushes back (O(1)). There's no metadata per allocation, no coalescing, no lock if single-threaded.
Trade-offs: allocation is deterministic and fast (~10-20ns vs 100-300ns for malloc). Memory fragmentation is eliminated because all slots are same size (no external fragmentation), but internal fragmentation increases if objects are smaller than the slot. The pool never returns memory to the OS until destructed, so peak usage becomes permanent memory overhead. Pools work best for fixed-size, high-frequency allocations with known lifetimes.
Q02 of 04SENIOR
How would you make a pool allocator thread-safe? Compare mutex, lock-free, and thread-local approaches.
ANSWER
1. Mutex: Protect allocate/deallocate with a std::mutex. Simple, but under contention the lock becomes a bottleneck — overhead can be 500ns per operation.
2. Lock-free: Use std::atomic <Slot*> for the free head. Use CAS to pop/push. Avoids OS-level locks but is vulnerable to the ABA problem. Works well for moderate contention (~4-8 threads).
3. Thread-local pools: Each thread has its own pool. No locks, no ABA. But objects allocated on thread A cannot be freed on thread B unless you implement a transfer queue. On NUMA systems, memory is allocated near the thread's CPU, improving cache locality.
I'd choose thread-local whenever the allocation pattern allows thread-affine free. For cross-thread frees, I'd use a global lock-free pool combined with a per-thread free cache.
Q03 of 04SENIOR
Explain the ABA problem in lock-free data structures and how it affects pool allocators.
ANSWER
The ABA problem occurs when a thread reads address A, another thread modifies the list and changes the head to A again, and the first thread's CAS succeeds even though the list structure changed (e.g., a node was removed and re-added).
In a lock-free pool, imagine thread T pops node P. Meanwhile thread U pops P, modifies its next pointer, and pushes it back. When T's CAS compares the head, it sees P again — but the head points to a different next node. T will successfully pop P, but now the list is corrupted because P's next no longer points to the correct successor.
Solutions: 1) Use tagged pointers (attach a version counter to the pointer). 2) Use hazard pointers (mark nodes as in-use before reading them). 3) Use RCU (read-copy-update). For pool allocators, a simpler fix is to use a double-CAS on the head and next pointer, or restrict the pool size so that ABA is statistically improbable.
Q04 of 04SENIOR
What is the difference between a pool allocator, an arena allocator, and a slab allocator?
ANSWER
- Pool allocator: Pre-allocates fixed-size slots. Each slot can be individually freed and reused. Best for objects of a single type.
- Arena allocator: Allocates a large block and advances a pointer (bump allocator). Frees only happen when the entire arena is reset. No individual free. Extremely fast (~2-3ns) — used for temporary computations.
- Slab allocator: Multiple fixed-size pools for different size classes. Allocs are rounded up to the nearest slab size. Combines the speed of pools with flexibility for multiple sizes. Used in Linux kernel and jemalloc.
If you need individual free, use a pool or slab. If you only need to free all at once (e.g., per-frame in a game), use an arena.
01
Describe how a fixed-size pool allocator works internally. What are the trade-offs compared to malloc?
SENIOR
02
How would you make a pool allocator thread-safe? Compare mutex, lock-free, and thread-local approaches.
SENIOR
03
Explain the ABA problem in lock-free data structures and how it affects pool allocators.
SENIOR
04
What is the difference between a pool allocator, an arena allocator, and a slab allocator?
SENIOR
FAQ · 5 QUESTIONS
Frequently Asked Questions
01
What is a memory pool allocator in simple terms?
A memory pool allocator is like a pre-stocked tray of glasses at a bar. Instead of washing a glass (allocating) each time someone orders, the bartender grabs a clean glass from the stack instantly. When the glass is returned, it goes back onto the stack. No searching, no washing — just a swap.
Was this helpful?
02
When should I NOT use a memory pool allocator?
Avoid pools when: 1) Objects have varying sizes (internal fragmentation hurts). 2) Peak usage is far above steady state (memory is pinned forever). 3) Allocations are rare (the complexity isn't worth it). 4) You need reliable leak detection (pools mask leaks). 5) Objects outlive the pool's lifetime (use-after-free risk).
Was this helpful?
03
Can I use a pool allocator with std::vector?
Yes — write a custom STL allocator that wraps your pool. But be careful: std::vector::resize(n) allocates n objects at once. Your pool must support multi-slot allocation or you'll get bad_alloc. Prefer reserve() and push_back() to trigger single allocations.
Was this helpful?
04
How do I debug a pool allocator corruption?
Enable poison values: fill freed slots with a known pattern (0xDEADBEEF or 0xCD). Also fill allocated slots with a different pattern. When you crash, the value tells you if the pointer is freed or not (use-after-free check). Run with AddressSanitizer (ASan) — it can catch intra-pool violations if you tell it about the pool memory region via __asan_poison_memory_region().
Was this helpful?
05
What is the difference between a pool allocator and an arena allocator?
A pool allocator allows individual objects to be freed and reused individually. An arena (or bump allocator) only allows freeing all objects at once by resetting a pointer. Arenas are faster (~2ns vs 10-20ns for pools) but less flexible. Use arenas for temporary work (e.g., per-frame allocations in a game) and pools for long-lived objects that need reclamation.