Explain the position values. Then explain why a higher z-index sometimes still renders BEHIND a lower one.
Whether you understand containing blocks and — the real trap — stacking contexts.
position values: static (default, in normal flow); relative (offset from its normal spot, still occupies original space, becomes a positioning context for children); absolute (removed from flow, positioned against the nearest positioned ancestor); fixed (against the viewport, ignores scroll); sticky (relative until it hits a scroll threshold, then fixed within its container). top/left/right/bottom only affect positioned elements. The z-index trap: z-index only orders elements WITHIN THE SAME stacking context. A stacking context is a self-contained layer; once an element forms one, ALL its descendants are painted as a group at that context's level, so a child with z-index: 9999 can never escape above a sibling context with z-index: 2. Contexts are created by: the root element, position + a z-index other than auto, opacity < 1, transform / filter / will-change, isolation: isolate, and flex/grid children with a z-index. So the fix for 'my huge z-index does nothing' is usually to raise the z-index of the ANCESTOR that forms the competing context, or to stop creating an unwanted context (e.g. a stray opacity: 0.99).
Modals, dropdowns, and tooltips that render behind other content despite a big z-index — trace the stacking contexts.