Why this matters: the core design is ten lines, so the depth interviewers look for lives in the neighborhood: how the costs move when the parameters move, why the standard library made slightly different promises than you did, and how you harden a design whose entire correctness hangs on eight bytes. This is the material that turns a correct answer into a senior one.
When alignment gets big
Our overhead is bounded by alignment − 1 + 8 per allocation. At cache-line alignment (64) that's at most 71 bytes — noise. At page alignment (4096) it's nearly 4 KiB of potential padding per allocation, and for small payloads the padding can dwarf the data. The honest statement is that the over-allocate-and-round design has overhead linear in the alignment, which makes it the right tool for SIMD- and cache-line-scale requests and an increasingly wasteful one as alignment approaches page size.
What lies beyond it is worth naming, not building: page-aligned memory is what the operating system's own mapping primitives hand out naturally, so real allocators route huge-alignment requests to a different mechanism entirely rather than paying the padding. Inside this problem's constraint — malloc only — the honest mitigation is to state the waste and, if many same-sized page-aligned requests are expected, amortize by carving several from one over-allocated region. Knowing where your design's regime ends is the senior answer.
Stash versus side table, revisited properly
Lesson 02 rejected the side table on the no-shared-state rule. It deserves a fairer trial here, because the trade is real and interviewers sometimes push on it:
stash (chosen) side table
lookup O(1), no contention hash lookup + lock (or a
concurrent map's overhead)
per-alloc overhead 8 bytes adjacent table entry elsewhere
(plus its own allocation)
failure mode corruptible by a buffer survives payload underruns;
UNDERRUN just before P corrupts on table bugs instead
alignment of costs cost travels with the cost centralized; visible in
allocation one place, easy to instrument
The side table's genuine advantage is robustness against caller underruns — a bug that scribbles just before the returned pointer destroys the stash but leaves a table untouched — plus centralized observability. Its price is a lock (or lock-free machinery) on the hottest path and memory that scales with live allocations. For a general-purpose layer, the stash wins; for a debugging build, the table is genuinely attractive — which is a resolution interviewers like: the alternatives aren't wrong, they're tools for different builds.
What the standard APIs promise — and why it differs
Comparing your design's contract against the standard library's is a fast way to show contract literacy:
- posix_memalign(ptr_out, alignment, size) requires alignment to be a power of two and a multiple of pointer size, returns an error code rather than NULL, and — critically — its memory goes to plain
free. That last promise means the implementation cannot rely on a stash the way ours does; it must get suitably-placed memory from the allocator's internals. The API's ergonomics (error codes, out-params) are the price of that integration. - C11 aligned_alloc(alignment, size) additionally demands that
sizebe a multiple ofalignment— a stricter contract that simplifies implementations and pushes bookkeeping burden onto callers. Also freed with plainfree.
Your layer made the opposite trade: a dedicated aligned_free in exchange for building on an unmodified malloc. Being able to say why each API drew its line where it did — integration versus layering — is exactly the "contracts are designs" lesson this problem teaches.
Canaries: hardening the eight bytes that matter
Everything hinges on the stash surviving until aligned_free reads it. A debug build can make violations loud:
layout (debug): ... | canary | stash | payload ...
0xA11C0C0A raw P
aligned_free: verify canary == 0xA11C0C0A before trusting the stash;
on mismatch: report corruption, do NOT free garbage
The canary costs eight more bytes and one comparison, and converts the two nastiest failure modes — caller underrun and plain-free/aligned-free mixing — from silent heap corruption into an immediate, attributable report. Poisoning the stash after reading it (so double aligned_free trips the check too) completes the picture. None of this ships in the release build; saying so, unprompted, shows you know hardening is a build mode, not a tax on the hot path.
Key takeaway
The design space is the cost model and the contract: overhead grows linearly with alignment (know where the regime ends), the stash beats the side table for release builds while the table earns its keep in debug ones, the standard APIs traded a dedicated free for allocator integration — and one canary plus poisoning turns the design's single point of fragility into a loud, attributable failure.