Exercise 3: Reproducing the Global z-index Bug and Blaming the Right Function — Possible Solution ==================================================================== THE TEST ------------------------------ child_c = block({'background-color': 'blue', 'z-index': '0'}) parent_p = block({'background-color': 'red', 'z-index': '5', ...}, children=[child_c]) layout_block(parent_p, root_cb) naive_list = build_display_list_naive_global_zindex(parent_p) fixed_list = build_display_list_zindex(parent_p) color_at_point(naive_list, 30, 10) # inside both P and C color_at_point(fixed_list, 30, 10) RESULT ------------------------------ naive_list order -> ['blue', 'red'] -- color_at_point -> 'red' (WRONG) fixed_list order -> ['red', 'blue'] -- color_at_point -> 'blue' (correct) WHY collect_all_boxes IS THE ROOT CAUSE, NOT get_z_index ------------------------------ get_z_index(parent_p) correctly returns 5. get_z_index(child_c) correctly returns 0. Both of these are exactly right -- there is nothing wrong with how either box's own z-index is being read or converted to a number. The problem is entirely in what build_display_list_naive_global_zindex does with those two correct numbers: def build_display_list_naive_global_zindex(root): all_boxes = collect_all_boxes(root) # <- the problem all_boxes_sorted = sorted(all_boxes, key=get_z_index) display_list = [] for box in all_boxes_sorted: paint_background(box, display_list) return display_list collect_all_boxes(root) flattens the ENTIRE tree -- parent AND every descendant -- into one single flat Python list, with no record left of which boxes were originally parents of which other boxes. Once that structural information is gone, sorted(all_boxes, key=get_z_index) has absolutely no way to know that parent_p and child_c have a parent/child relationship at all -- to the sort, they're just two unrelated items with numeric keys 5 and 0, exactly as interchangeable as if they were two unrelated siblings. The sort does exactly what a sort is supposed to do (put lower keys first), correctly, on the data it was given -- the data itself (a flat list that erased the tree structure) is what was wrong. WHY THIS MATTERS AS A DISTINCTION ------------------------------ This is a genuinely important debugging distinction: when a program produces a wrong answer, the function that LOOKS like it's making the decision (here, sorted(), or even get_z_index() since it's the thing literally producing the numbers being compared) isn't automatically the function AT FAULT. Here, the fault lies one step earlier, in a function (collect_all_boxes) that discarded information (tree structure) that turned out to be necessary for the LATER step to behave correctly. The fix, correspondingly, isn't a change to get_z_index or to sorted()'s own key function -- it's a change to WHEN and WHERE sorting happens: build_display_list_zindex never flattens the tree at all, and instead sorts each box's own IMMEDIATE children list, one recursion level at a time, which is exactly the structural information collect_all_boxes threw away. WHY THIS WORKS AS AN ANSWER ------------------------------ Confirming color_at_point disagrees between the two versions proves the bug has a real, visible consequence (not just a theoretical structural concern), while tracing the fault back to collect_all_boxes specifically -- rather than to get_z_index, which a first glance might plausibly suspect -- demonstrates that fixing this bug correctly requires understanding WHERE information was lost, not just which function's own output looked wrong.