PyTorch Interview Questions: Training Loop

Reviewed by Mark Dickie · Last updated

A PyTorch training loop is the block of code that repeatedly performs a forward pass, computes a loss, runs backpropagation, and applies an optimizer step to update model weights. For interviews, you should be able to write one from memory and explain each stage: clearing gradients with optimizer.zero_grad(), calling loss.backward() to populate .grad tensors, and calling optimizer.step() to apply the update. Interviewers also expect you to know why gradient accumulation happens when you forget to zero gradients, what model.train() versus model.eval() switches on, and how to move batches to the correct device.

What does a PyTorch training loop interview test?

The core skill is writing a correct loop and reasoning about what each call does under the hood. You will also be asked about device placement, mixed precision with torch.amp, gradient clipping, and the difference between loss.backward() and optimizer.step().

ConceptWhat to know
optimizer.zero_grad()Resets .grad to zero before each batch; skipping it causes gradients to accumulate
loss.backward()Triggers autograd backprop and fills .grad on every leaf tensor
optimizer.step()Applies the optimizer rule (SGD, Adam, etc.) using the accumulated .grad values
model.train() / model.eval()Toggles dropout and batch norm running-stat behavior
torch.no_grad()Disables autograd graph construction during validation or inference

How do you structure a basic training loop in PyTorch?

A minimal loop has a predictable order of calls, and interviewers want to see that order memorized:

  1. Iterate over the DataLoader to pull batches of inputs and targets.
  2. Move inputs and targets to the model's device with .to(device).
  3. Call optimizer.zero_grad() so stale gradients do not leak into this step.
  4. Run outputs = model(inputs) to produce predictions.
  5. Compute loss = criterion(outputs, targets) against the target labels.
  6. Call loss.backward() to populate parameter .grad tensors through autograd.
  7. Call optimizer.step() to update the weights.
  8. Optionally clip gradients with torch.nn.utils.clip_grad_norm_ before the step.

What mistakes do interviewers look for?

Common errors include forgetting zero_grad (gradients pile up across batches), calling step() before backward() (no gradients exist yet), and leaving the model in train() mode during validation so batch norm keeps updating running statistics. Another frequent question is why a loss that never decreases can come from passing raw logits to a loss function that already applies softmax internally, doubling the activation.

Key facts

  • Tarmac has 25 PyTorch interview questions on this topic, 10 of them on this page, at difficulty 1–4 of 5.
  • Tarmac tracked 181 job postings asking for PyTorch in August 2026.
  • Roles asking for PyTorch advertise a median base salary of £95,000, across 23 job postings as of August 2026.
  • Tarmac last reviewed these PyTorch interview questions on 21 September 2026.

At a glance

Questions10 shown · 25 in the bank
Difficulty1–4 of 5
FormatsTrue / false, Multiple choice, Fill in the blank, Code output, Find the bug

What you'll review

  1. training loop pytorch
  2. loss functions pytorch

Practice questions

Try one before you open the answer. Pick an option and press Check; it's marked on the spot.

PyTorch/training-loop-pytorch

In a standard PyTorch training loop, if you do not call optimizer.zero_grad() before loss.backward(), gradients from the previous batch will accumulate (sum) into the same .grad tensors instead of being replaced.#

Options

Show answer

True. PyTorch accumulates gradients into .grad by design, so without calling optimizer.zero_grad() (or setting grads to zero) before loss.backward(), the new gradients are added to the previous batch's gradients rather than replacing them. This is intentional for gradient accumulation but is a common bug in a standard per-batch training loop.

Why:

PyTorch accumulates gradients into .grad by design — this enables gradient accumulation across micro-batches. When you want per-batch updates, you must zero existing gradients (typically with optimizer.zero_grad()) before the next backward() call; otherwise the new gradients are added to whatever was already in .grad.

PyTorch/training-loop-pytorch/loss-functions-pytorch

In a PyTorch training loop, you are training a classifier whose final layer is a nn.Linear with out_features=num_classes (raw logits, no softmax applied). Which loss function is the standard, correct choice for multi-class classification with integer class labels?#

Options

Show answer

Use nn.CrossEntropyLoss() on the raw logits for multi-class classification with integer class labels. PyTorch's CrossEntropyLoss internally applies log_softmax then NLLLoss, so the model's final layer should output raw logits without a softmax. NLLLoss alone expects log-probabilities, and BCELoss is for binary or multi-label settings.

Why:

nn.CrossEntropyLoss expects raw logits (shape [N, C]) and integer target labels (shape [N]). It internally combines log_softmax and NLLLoss, so passing logits is the correct usage. nn.NLLLoss requires log-probabilities (the output of log_softmax), not raw logits. nn.BCELoss is for binary or multi-label classification and expects probabilities in [0, 1]. nn.MSELoss is a regression loss and is not appropriate for classification with integer labels.

PyTorch/training-loop-pytorch/loss-functions-pytorch

In a standard PyTorch training loop, after computing the loss tensor with loss = criterion(outputs, labels), the next two steps are to call loss._____() to populate .grad on all leaf tensors that require gradients, then call optimizer._____() to update the model parameters using those gradients. (Before the next iteration you would also call optimizer.zero_grad().)#

Show answer

In a standard PyTorch training loop, after computing the loss tensor with loss = criterion(outputs, labels), the next two steps are to call loss.**backward**() to populate .grad on all leaf tensors that require gradients, then call optimizer.**step**() to update the model parameters using those gradients. (Before the next iteration you would also call optimizer.zero_grad().)

Why:

loss.backward() walks the autograd graph from the loss tensor backward, accumulating gradients into .grad on every leaf tensor that has requires_grad=True. optimizer.step() then applies the stored gradients to the parameters according to the optimizer's update rule (e.g., SGD, Adam).

PyTorch/training-loop-pytorch

Given the following PyTorch training snippet, what is printed to stdout? Assume all imports are present and torch seeds are irrelevant because weights are set explicitly.#

import torch
import torch.nn as nn

model = nn.Linear(2, 1, bias=False)
model.weight.data = torch.tensor([[2.0, 2.0]])

criterion = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)

x = torch.tensor([[1.0, 1.0]])
y = torch.tensor([[0.0]])

for epoch in range(3):
    optimizer.zero_grad()
    out = model(x)
    loss = criterion(out, y)
    loss.backward()
    optimizer.step()
    print(round(model.weight[0, 0].item(), 4))
Show answer
1.2
0.72
0.432
Why:

Each epoch: zero gradients, compute forward, compute MSE loss, backprop, then SGD step. With w0 = w1 = 2 initially: out = 2+2 = 4, loss = 16, grad of loss w.r.t. weight = 2*(out−y)/N * x = 241 = 8, so w0 = 2 − 0.18 = 1.2 (printed). Epoch 1: out = 1.2+1.2 = 2.4, grad = 22.4 = 4.8, w0 = 1.2 − 0.48 = 0.72 (printed). Epoch 2: out = 0.72+0.72 = 1.44, grad = 2*1.44 = 2.88, w0 = 0.72 − 0.288 = 0.432 (printed as 0.432).

PyTorch/training-loop-pytorch

The following PyTorch training loop is intended to update model weights using per-batch gradients. It contains a single bug. Identify the buggy line number (1-based).#

import torch
import torch.nn as nn

model = nn.Linear(10, 1)
criterion = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

for inputs, labels in dataloader:
    optimizer.zero_grad()
    outputs = model(inputs)
    loss = criterion(outputs, labels)
    optimizer.step()
    loss.backward()
Show answer

The bug is on line 12.

Why:

The optimizer steps (line 12) before gradients are computed by loss.backward() (line 13). In PyTorch, optimizer.step() applies whatever gradients are currently stored in .grad; since loss.backward() has not run yet on the current batch, the update either uses stale gradients from the previous batch or no gradients at all on the first iteration. The correct order is loss.backward() followed by optimizer.step(), so line 12 is the buggy line — it must appear after line 13.

PyTorch/training-loop-pytorch/loss-functions-pytorch

What value is printed by the following PyTorch snippet? Round your answer to 4 decimal places.#

import torch
import torch.nn as nn

logits = torch.tensor([[2.0, 1.0, 0.1]])
target = torch.tensor([0])
criterion = nn.CrossEntropyLoss()
loss = criterion(logits, target)
print(round(loss.item(), 4))
Show answer
0.4168
Why:

nn.CrossEntropyLoss combines log_softmax and NLLLoss on raw logits. For the row [2.0, 1.0, 0.1] with target class 0, the log-softmax value at index 0 is 2.0 − ln(e^2.0 + e^1.0 + e^0.1) = 2.0 − ln(7.389 + 2.718 + 1.105) = 2.0 − ln(11.212) ≈ 2.0 − 2.4168 = −0.4168. CrossEntropyLoss negates this, giving ≈ 0.4168.

PyTorch/training-loop-pytorch/loss-functions-pytorch

In a PyTorch training loop for a 3-class classifier, your model's final linear layer outputs raw logits (no softmax applied). Which built-in loss function should you use so that gradients are computed correctly without manually applying softmax?#

Options

Show answer

Use nn.CrossEntropyLoss when your model outputs raw logits for multi-class classification. It internally applies log_softmax followed by NLLLoss, so you should not apply a softmax layer yourself. nn.NLLLoss expects log-probabilities (not raw logits), nn.BCELoss expects probabilities (not logits), and nn.MSELoss is for regression, not classification.

Why:

nn.CrossEntropyLoss expects raw logits and applies log_softmax + NLLLoss internally, so applying softmax yourself would double-transform the probabilities. nn.NLLLoss expects log-probabilities as input (e.g., output of LogSoftmax), not raw logits. nn.BCELoss expects probabilities in [0, 1] (use BCEWithLogitsLoss for raw logits in binary settings). nn.MSELoss is for regression and is not appropriate for classification targets.

PyTorch/training-loop-pytorch

Trace the following PyTorch training loop that implements SGD with momentum manually (without using torch.optim). The code runs on CPU with two training examples processed sequentially. What is the exact printed output?#

import torch

torch.manual_seed(42)

w = torch.tensor(1.0, requires_grad=True)
b = torch.tensor(0.0, requires_grad=True)

lr = 0.1
momentum = 0.9

v_w = torch.tensor(0.0)
v_b = torch.tensor(0.0)

data = [
    (torch.tensor(2.0), torch.tensor(5.0)),
    (torch.tensor(3.0), torch.tensor(7.0)),
]

for x, y_true in data:
    y_pred = w * x + b
    loss = (y_pred - y_true) ** 2
    loss.backward()

    with torch.no_grad():
        v_w = momentum * v_w + w.grad
        v_b = momentum * v_b + b.grad
        w -= lr * v_w
        b -= lr * v_b
        w.grad.zero_()
        b.grad.zero_()

    print(f"w={w.item():.4f}, b={b.item():.4f}")
Show answer
w=2.2000, b=0.6000
w=3.1600, b=1.1000
Why:

Step 1 (x=2.0, y_true=5.0): y_pred = 1.02.0 + 0.0 = 2.0; loss = (2.0−5.0)² = 9.0. Autograd gives dw = 2(2−5)2 = −12, db = 2(2−5)1 = −6. Momentum buffers: v_w = 0.90 + (−12) = −12; v_b = 0.90 + (−6) = −6. Update: w = 1.0 − 0.1(−12) = 2.2; b = 0.0 − 0.1*(−6) = 0.6. Output: w=2.2000, b=0.6000.

Step 2 (x=3.0, y_true=7.0): y_pred = 2.23.0 + 0.6 = 7.2; loss = (7.2−7.0)² = 0.04. Gradients (accumulated onto zeroed .grad): dw = 2(7.2−7.0)3 = 1.2; db = 2(7.2−7.0)1 = 0.4. Momentum: v_w = 0.9(−12) + 1.2 = −9.6; v_b = 0.9*(−6) + 0.4 = −5.0. Update: w = 2.2 − 0.1*(−9.6) = 3.16; b = 0.6 − 0.1*(−5.0) = 1.1. Output: w=3.1600, b=1.1000. The torch.manual_seed(42) call has no effect because no stochastic operations are used.

PyTorch/training-loop-pytorch

The following PyTorch training loop is intended to train a classifier with BatchNorm and Dropout for 50 epochs, validating each epoch. The model trains (loss decreases) but performs poorly and never generalizes. Identify the buggy line.#

import torch
import torch.nn as nn

class Net(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc1 = nn.Linear(784, 256)
        self.bn1 = nn.BatchNorm1d(256)
        self.drop = nn.Dropout(0.5)
        self.fc2 = nn.Linear(256, 10)

    def forward(self, x):
        x = torch.relu(self.fc1(x))
        x = self.bn1(x)
        x = self.drop(x)
        return self.fc2(x)

model = Net()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
criterion = nn.CrossEntropyLoss()

for epoch in range(50):
    model.eval()
    for x, y in train_loader:
        optimizer.zero_grad()
        out = model(x)
        loss = criterion(out, y)
        loss.backward()
        optimizer.step()

    model.eval()
    with torch.no_grad():
        for x, y in val_loader:
            val_loss = criterion(model(x), y)
Show answer

The bug is on line 23.

Why:

Line 23 calls model.eval() at the top of each training epoch instead of model.train(). In eval mode, nn.BatchNorm1d uses its running statistics (initialized to mean=0, var=1) rather than per-batch statistics, and crucially it does not update those running stats — so they remain at their initial values forever. Additionally, nn.Dropout(0.5) becomes an identity operation in eval mode, disabling all regularization. The model can still partially fit (Adam adapts), which is why loss decreases, but the BatchNorm layer provides no normalization benefit and dropout provides no regularization, leading to poor generalization. The fix is to change line 23 to model.train(). Line 31's model.eval() for validation is correct.

PyTorch/training-loop-pytorch/loss-functions-pytorch

The following training loop compiles and runs without error, but the model trains poorly — the loss decreases very slowly and accuracy plateaus well below what the architecture should achieve. Find the bug.#

import torch
import torch.nn as nn
import torch.nn.functional as F

class Classifier(nn.Module):
    def __init__(self, num_classes):
        super().__init__()
        self.fc = nn.Linear(256, num_classes)

    def forward(self, x):
        x = self.fc(x)
        return F.softmax(x, dim=1)

model = Classifier(10)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)

for inputs, targets in train_loader:
    outputs = model(inputs)
    loss = criterion(outputs, targets)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
Show answer

The bug is on line 12.

Why:

Line 12 applies F.softmax(x, dim=1) inside the model's forward, returning class probabilities. nn.CrossEntropyLoss expects raw logits — it internally applies log_softmax followed by NLLLoss. Feeding already-softmaxed probabilities into CrossEntropyLoss effectively computes log_softmax(softmax(x)), a double-softmax that compresses the output distribution and produces vanishingly small gradients far from the decision boundary. The fix is to return x (the raw logits) from forward and let CrossEntropyLoss handle the softmax internally. If probabilities are needed for inference, apply softmax separately at prediction time, not during the forward pass used for training.

Related interview questions

Job market

See pytorch salaries and hiring demand from live job postings.

The other 15 questions

This page shows 10 and marks what you pick. That's as far as a page can go. A free account opens the other 15 and keeps every answer. What you miss comes back until it's right: after a day, then at longer gaps.

Start with this topic

Free · the whole bank · 100 marked answers per 30 days · written feedback on the paid plan

What moved, monthly

One email a month when the bulletin comes out: what moved in the markets we track, and the new question topics we published. Confirm your address to join. Unsubscribe any time.