Exercise 1: Why free_space() Drops by 16 Bytes, Not 8 — Possible Solution ==================================================================== THE TEST ------------------------------ p = Page() initial_free = p.free_space() p.add_record(b'12345678') # 8 bytes after_one = p.free_space() RESULT ------------------------------ initial_free -> 60 (PAGE_SIZE=64 minus HEADER_SIZE=4) after_one -> 44 drop: 60 - 44 = 16 bytes WHY THE DROP IS 16, NOT 8 ------------------------------ free_space() is defined as: def free_space(self): return self.free_space_end - (HEADER_SIZE + self.num_slots * SLOT_SIZE) This is the gap between where the slot directory currently ENDS (HEADER_SIZE + num_slots*SLOT_SIZE) and where the record data currently BEGINS (free_space_end) -- the genuinely usable middle region. Adding one 8-byte record changes BOTH sides of that gap at once: 1. free_space_end moves DOWN by exactly the record's own length (8 bytes) -- the record data claims 8 bytes from the data-growing end. 2. num_slots increases by 1, so HEADER_SIZE + num_slots*SLOT_SIZE moves UP by exactly SLOT_SIZE (8 bytes) -- the slot directory claims 8 bytes from the slot-growing end. Since free_space() is the DIFFERENCE between these two boundaries, and one boundary moved down by 8 while the other moved up by 8, the total gap shrinks by 8 + 8 = 16 -- both movements eat into the same shared middle region from opposite directions at once. WHY THIS WORKS AS AN ANSWER ------------------------------ This is the direct, numeric confirmation of the chapter's own central finding: storing ANY record costs the page two separate things, not one -- the record's own raw bytes, AND a fixed SLOT_SIZE-byte entry in the slot directory to describe where those bytes are. free_space() correctly reflects both costs together because it's defined purely in terms of the two boundaries that each cost individually moves -- there's no separate "subtract the slot overhead" step bolted on afterward; it falls out naturally from tracking where the two regions currently sit.