The footer you have been writing all along finally pays off. Today Free reads the previous block's footer to merge backward too, so a block freed between two free neighbours fuses all three into one.
On free, merge with the preceding block using its footer, and handle the case where both neighbours are free.
Merging backward means finding where the previous block starts - and that is exactly what the footer is for. The word in the 8 bytes just before your block is the previous block’s footer, carrying its size and flag. If that block is free, subtract its size from your offset to find its start, pull it out of the free list, and extend your block backward over it. Constant time, no searching - the whole reason every block carries a footer.
Do the forward merge and the backward merge together and you handle both neighbours at once: a block freed between two free blocks swallows the successor, then the predecessor, collapsing three blocks into one. With this, the allocator maintains a clean invariant - no two free blocks are ever adjacent - which keeps fragmentation in check and is one of the things the heap checker will later verify.
// before inserting, look at the previous block via its footer:if off > 0 {pfoot := u64(a.buf, off-8) // previous block's footerif !allocated(pfoot) {psize := sizeOf(pfoot)prev := off - psizea.unlink(prev)off, size = prev, size+psize // extend backwarda.putBlock(off, size, false)}}// combine with the forward-merge from last lesson to handle both sides