Every piece is now built: Graph node (value + grad + backward fn) gave every Value a local _backward rule and the ability to record its own parents. Topological sort for backward pass gave a way to order every node in a graph so processing it in reverse guarantees each node's OWN gradient is fully accumulated before it needs to push gradient further backward. This question is the payoff: wire those two pieces together into backward(root), a single function that computes gradients for an ENTIRE expression, of any depth, with any amount of variable reuse, with one call.
This is, in miniature, exactly what loss.backward() does in real PyTorch, 03-tanh, 04-softmax, every hand-derived gradient this curriculum has written up to this point was doing, by hand, precisely what this one function now does automatically, for any expression built from + and *.
Theory seeds the output node's gradient at 1.0 (the starting point, "how much does the output change with respect to itself"), builds the topological order from that output, and calls every node's _backward in REVERSE topological order, guaranteeing each node's gradient is complete before it's used to push gradient further back.
Implement backward(root) against that reasoning. The signature and docstring are already in the editor.
root.grad = 1.0 before calling any _backward.backward(root) runs, every reachable Value's .grad must be correct, verified against a finite-difference check or real PyTorch, not just "it runs without error."Open one at a time. Each gives away a little more than the last.
build_topo_order(root) (already imported) gives you the forward-dependency order; Python's reversed(...) turns that into the backward-processing order directly.
The whole function is four lines: get the topo order, set root.grad = 1.0, loop over reversed(topo_order), call node._backward() each time.
Click "Run Tests" to test your implementation