Question bank — Autograd and computational graphs in PyTorch
This is a rapid-fire trap bank for Autograd and computational graphs in PyTorch. Each item hides a specific misconception. Cover the answer side, say your reasoning out loud, then reveal. If you can only say "true/false" without the why, you don't know it yet.
Before we start, the words we lean on everywhere below, defined plainly:
The picture below is the mental model behind almost every answer: a forward chain of tensors and operations, with slopes flowing back along the same edges.

True or false — justify
TF1. A tensor created with requires_grad=False can never end up in the computational graph.
requires_grad=True tensor, the resulting tensor tracks gradients — the graph forms wherever at least one input needs a gradient.TF2. Calling .backward() on a tensor that is not a scalar works with no extra arguments.
.backward() on) that seed is . A non-scalar has many outputs, so there is no single seed — you must pass a gradient= vector of the same shape saying how to weight each element.TF3. .grad holds the value from your most recent .backward() call.
.grad accumulates — each .backward() adds to whatever was already there. That is why x.grad.zero_() exists.TF4. A leaf tensor (one you created directly, not from an operation) always has grad_fn=None.
grad_fn records which operation produced a tensor. A leaf was produced by you, not by an op, so it has no grad_fn; its gradient lands in .grad instead.TF5. Reassigning x = x + 1 on a requires_grad=True leaf keeps x a leaf.
x now points to a new tensor made by the + op (it has a grad_fn, so it is a non-leaf). Its .grad stays None; only the original leaf tensor (which you can no longer reach by the name x) would ever accumulate a gradient. See the sketch below.
TF6. Every time you run the forward pass, PyTorch reuses the same graph.
.backward(). This is what lets control flow like if/loops change the graph run to run.TF7. with torch.no_grad(): makes tensors lose their existing .grad values.
.grad values are untouched; you just can't backprop through anything computed inside the block.TF8. A tensor's .grad and the tensor's data have the same shape.
Spot the error
SE1. "I'll call loss.backward() every mini-batch and my weights will update correctly without zeroing grads." — What breaks?
optimizer.zero_grad() (the optimizer's reset method, or the raw .grad.zero_()), gradients from previous batches keep piling on, so each step uses a stale sum of many batches instead of the current one. The sketch below shows the pile-up. See gradient descent variants.
SE2. "y.backward() then y.backward() again on the same y — should be fine." — What breaks?
RuntimeError: graph freed. The graph is used-once by default. Pass retain_graph=True on the first call only if you truly need to backprop twice.SE3. "I set requires_grad=True on my input images so the model trains." — What's confused here?
requires_grad=False; you set it on the learnable tensors, which torch.nn handles for you (see nn.Module).SE4. "I did x.grad += something to nudge gradients, and got a graph-tracking error." — Why?
.grad edits in with torch.no_grad(): so they aren't recorded.SE5. "My detached tensor still updates the encoder through backprop." — Why is that impossible?
.detach() returns a tensor sharing the same data but cut off from the graph (no grad_fn). Gradients stop dead there, so nothing upstream (the encoder) receives them — the sketch below shows the severed edge.
SE6. "I converted a tensor with .numpy() mid-graph and gradients vanished." — What happened?
.numpy() only works on tensors outside the graph, so you were forced to .detach() first (or it errored). NumPy has no autograd, so any path through it is severed.SE7. "ReLU has no derivative at 0, so autograd crashes there." — Why doesn't it?
Why questions
WHY1. Why does the backward pass sum contributions over all children of a node?

WHY2. Why must the thing you call .backward() on be a scalar (by default)?
.backward() on) with seed slope . With many outputs there is no single "1" to start from — you'd have to say how much each output matters, i.e. supply a weighting vector.WHY3. Why does PyTorch build the graph during the forward pass rather than ahead of time?
ifs, variable lengths. This makes debugging and dynamic architectures natural, at the cost of rebuilding each pass.WHY4. Why does PyTorch free the graph after .backward() by default?
WHY5. Why does gradient accumulation actually help mini-batch training instead of being a bug?
WHY6. Why do frozen layers in transfer learning set requires_grad=False instead of just skipping them?
requires_grad=False tells autograd not to record ops or compute grads for those weights, so backprop stops there — saving compute and guaranteeing they never update.WHY7. Why can .backward() fill in gradients for parameters you never mentioned in the call?
grad_fn edges. The backward walk visits every reachable node with requires_grad=True, not just the ones you named.Edge cases
EC1. What is x.grad for a requires_grad=True tensor before you ever call .backward()?
None. No backward pass has run, so no slope has been computed or accumulated yet.EC2. A tensor is used in computing the loss but its value happens not to change the loss (e.g. multiplied by a branch that is zero) — what does its .grad come out as?
0.0 tensor. It was reached by the backward walk, so it gets a real gradient, and that gradient correctly says "nudging it changes the loss by nothing." (Contrast: a tensor never reached at all stays None, as in EC1.)EC3. What happens to gradients if the loss is exactly constant (e.g. loss = torch.tensor(3.0) with no graph)?
.backward() errors by default — a constant with no grad_fn has no path back to any parameter to differentiate. (If you truly needed to seed it, you would have to pass an explicit gradient= argument, but with no graph there is still nothing downstream to receive it.)EC4. During evaluation you forget torch.no_grad(). What still goes wrong even if you never call .backward()?
EC5. In batch normalization, running mean/variance buffers are updated during forward. Do they get gradients?
requires_grad); they are updated by running statistics, not by backprop, so autograd leaves them alone.EC6. In Python you write x = torch.tensor([2.0], requires_grad=True) then y = x ** 2 (so y is computed from x), and you call y.backward() twice with retain_graph=True and no zeroing in between. What is x.grad after the second call?
EC7. A tensor is built from two branches where one branch used .detach(). Does the other branch still get gradients?
grad_fn path, so gradients still flow through it normally.Recall Fastest self-check
If you can answer these three you own the page: What does .grad do between two .backward() calls without zeroing? Why must the backward root be scalar? What does .detach() cut?
.grad accumulates (adds), the root needs a single seed slope of 1, and .detach() cuts the graph link so gradients stop there. ::: Correct if you said accumulate / scalar seed / cut-the-link.