Plain recursion can't be paused mid-traversal -- once you call
render(child) from inside render(parent), you're committed to the
whole subtree before control returns. React's fiber architecture solves
this by replacing the call stack with an explicit linked structure: each
fiber node points to its first child, its next sibling, and its
parent (React calls this last one return). Walking the tree becomes a
loop over "what's the next unit of work," which a scheduler can stop
between any two calls to.
A fiber is a plain dict: {"id": ..., "parent": ..., "child": ..., "sibling": ...}.
Implement:
build_fiber_tree(spec, parent=None)--specis a nested dict{"id": ..., "children": [spec, ...]}. Build and return the root fiber withchild/sibling/parentwired up (first child inchildrenbecomeschild; the rest are chained viasibling).perform_unit_of_work(fiber, visit_log)-- "begin work" onfiber(append("begin", fiber["id"])). If it has achild, that's the next unit -- return it. Otherwise delegate tocomplete_unit_of_work.complete_unit_of_work(fiber, visit_log)-- starting atfiber, repeatedly: append("complete", node["id"]); ifnodehas asibling, return it (next unit); otherwise move tonode["parent"]and repeat (the parent's work is ALSO now fully done, since its last child just finished). Reaching a node with no parent means the whole tree is done -- returnNone.work_loop(root, visit_log, unit_budget=None)-- callperform_unit_of_workrepeatedly, one unit per call, until the tree finishes orunit_budgetunits have run THIS call. Return the fiber to resume from (orNoneif finished) -- callingwork_loopagain with that returned fiber must continue exactly where it left off.
Example
spec = {"id": "root", "children": [
{"id": "a", "children": [{"id": "a1"}, {"id": "a2"}]},
{"id": "b"},
]}
root = build_fiber_tree(spec)
log = []
work_loop(root, log)
# log == [("begin","root"), ("begin","a"), ("begin","a1"), ("complete","a1"),
# ("begin","a2"), ("complete","a2"), ("complete","a"),
# ("begin","b"), ("complete","b"), ("complete","root")]
Note that completing a2 (the last child of a) immediately bubbles up
and ALSO completes a in the same step -- that's not a bug, it's the
real mechanism: a parent's work is only "complete" once its last child's
subtree is.
Constraints
unit_budgetcounts calls toperform_unit_of_work, notbegin/completelog entries (one call can append severalcompleteentries via the bubble-up loop).- Resuming with the fiber
work_loopreturned must produce a log identical to running the whole thing uninterrupted, just split across calls.
Real-world grounding
- React's fiber work loop:
packages/react-reconciler/src/ReactFiberWorkLoop.js-- seeperformUnitOfWorkandcompleteUnitOfWork, which this exercise implements a simplified version of.
Real React's workLoopConcurrent checks shouldYield() (a real deadline
tied to the browser's frame budget) between units instead of a fixed
unit_budget, and beginWork/completeWork do actual rendering work
(not just logging) -- this exercise implements the traversal shape both
are built on.