Design First-Fit and Best-Fit Memory Allocation
OpenAISoftware Engineer · Technical ScreenLast updated
Design an in-memory heap allocator supporting first-fit and best-fit allocation, block splitting, adjacent free block coalescing, and safe handle validation to prevent stale or forged free operations.
Problem Statement
Implement an in-memory allocator managing a single contiguous address space of size total_size bytes (with addresses ranging from 0 to total_size - 1). Your allocator must support dynamic block splitting, immediate coalescing of neighboring free blocks, and robust handle validation.
Requirements
- Allocation Strategies:
- First-Fit (
"FIRST_FIT"): Allocate from the free block with the lowest starting address that is large enough (size >= requested_size). - Best-Fit (
"BEST_FIT"): Allocate from the smallest free block that satisfiessize >= requested_size. Break ties by selecting the block with the lowest starting address.
- First-Fit (
- Splitting: When an allocated block is strictly larger than the requested size, split the block: the lower address portion becomes the allocated block, and the remainder remains free.
- Coalescing: When an allocated block is freed, immediately merge it with physically adjacent free blocks (both predecessor and successor, if free).
- Handle Safety:
allocatereturns an opaque handle identifier.freemust reject invalid or stale handles (e.g., handles that were never issued, already freed, or where the underlying byte range was reallocated to a new request) by returningFalsewithout modifying any allocator state.
Class Interface
class Allocator:
def __init__(self, total_size: int, strategy: str = "FIRST_FIT"):
"""
Initialize memory of total_size with strategy in {"FIRST_FIT", "BEST_FIT"}.
"""
pass
def allocate(self, size: int) -> int | None:
"""
Allocates `size` bytes. Returns an opaque integer handle on success,
or None if insufficient contiguous memory exists.
"""
pass
def free(self, handle: int) -> bool:
"""
Frees memory associated with `handle`.
Returns True on success, or False if the handle is invalid or stale.
"""
pass
Constraints
1 <= total_size <= 10^91 <= size <= total_size- At most
10,000total calls toallocateandfree. strategyis either"FIRST_FIT"or"BEST_FIT".
Example
alloc = Allocator(total_size=100, strategy="FIRST_FIT")
h1 = alloc.allocate(30) # Allocated [0, 30), remaining free [30, 100)
h2 = alloc.allocate(20) # Allocated [30, 50), remaining free [50, 100)
h3 = alloc.allocate(20) # Allocated [50, 70), remaining free [70, 100)
alloc.free(h2) # Block [30, 50) is now free; returns True
alloc.free(h2) # Returns False (stale / double-free)
alloc.free(99999) # Returns False (forged handle)
h4 = alloc.allocate(15) # Under FIRST_FIT, takes [30, 45); free [45, 50)
alloc.free(h1) # Free [0, 30)
alloc.free(h4) # Free [30, 45); coalesces [0, 30), [30, 45), [45, 50) into [0, 50)
Hints
Hint 1
Maintain an address-ordered linked list of memory blocks (or an interval map) so that predecessor and successor blocks can be inspected in O(1) time during coalescing.
Hint 2
To validate handles safely against double-frees or forged IDs, map each issued handle to its allocated block metadata and remove it upon deallocation.
Hint 3
For best-fit, search across all available free blocks to minimize `block.size - requested_size >= 0`, using the block's start address as the secondary tie-breaker.
Follow-up questions
- How would you support memory alignment requirements (e.g., allocations aligned to 8-, 16-, or 64-byte boundaries)?
- How can you improve best-fit lookup time from O(N) to O(log N) using segregated free lists or self-balancing binary search trees?
- How would you ensure thread safety across concurrent allocations and deallocations while minimizing lock contention?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.