Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ML & LLM Interview Q&A: 144 Questions

Comprehensive interview questions and answers for ML/LLM coding interviews.

Table of Contents

  1. Classical ML
  2. LLM Fundamentals
  3. LLM Inference
  4. Training Techniques
  5. Optimization
  6. Regularization
  7. Bias & Variance
  8. Information Theory
  9. Discriminative vs Generative Models
  10. Kernel Functions
  11. NLP Basics
  12. MLE and MAP Estimation
  13. Multimodal Models and Embeddings
  14. RAG (Retrieval-Augmented Generation)
  15. Linear and Logistic Regression Derivations
  16. RAG Retrieval Methods
  17. NLP Problems: Standard Solution Procedures
  18. Foundation Models: Evolution from BERT to GPT-4
  19. Multimodal Integration and World Models
  20. GPT Implementation, Training, and Decoding
  21. Prompt Tuning and Prefix Tuning
  22. Diffusion Models
  23. Perplexity and Related Concepts
  24. Causal Attention
  25. Advanced Attention Mechanisms (GQA, Paged Attention)
  26. Mixture of Experts (MoE)
  27. State Space Models (SSM)
  28. Classical ML: Trees, Ensembles, and Dimensionality
  29. Evaluation and Data Discipline
  30. Training Fundamentals
  31. Modern LLM Systems

Classical ML

Q1: Implement linear regression from scratch.

In 30 seconds. “Linear regression predicts a weighted sum of the features plus an intercept, and I score it with mean squared error. Fitting is just gradient descent: predict, take the residual, and push the weights in the direction that shrinks it — the weight gradient is X.T @ residual / n and the bias gradient is the mean residual. It’s a convex problem, so with a sane learning rate you land on the global optimum.”

The short version.

  • Model: . Loss: MSE.
  • Weight gradient: dw = X.T @ (y_pred - y) / n. Bias gradient: db = mean(y_pred - y).
  • Update: w -= lr * dw, b -= lr * db, repeat.
  • Convex bowl → one minimum, no local traps.
  • Standardise features first, or the biggest-scale column owns the step size.
class LinearRegression:
    def __init__(self, lr=0.01, n_iter=1000):
        self.lr = lr
        self.n_iter = n_iter
        self.weights = None
        self.bias = None
    
    def fit(self, X, y):
        n_samples, n_features = X.shape
        self.weights = np.zeros(n_features)
        self.bias = 0
        
        for _ in range(self.n_iter):
            y_pred = X.dot(self.weights) + self.bias
            dw = (1/n_samples) * X.T.dot(y_pred - y)
            db = (1/n_samples) * np.sum(y_pred - y)
            self.weights -= self.lr * dw
            self.bias -= self.lr * db
    
    def predict(self, X):
        return X.dot(self.weights) + self.bias

Why it works.

Linear regression assumes the target is a weighted sum of the features plus a constant offset, so the model is , where is the design matrix ( rows of data, features per row), is the length- vector of weights, and is a single scalar called the bias or intercept. “Fitting” means choosing and so the predictions are as close as possible to the observed targets, where “close” is measured by the mean squared error.

Gradient descent is the method used here. The gradient is the vector of partial derivatives; it points in the direction of steepest increase of the loss, so stepping in the negative gradient direction reduces the loss. Doing that repeatedly with a small step size walks downhill to the bottom of the bowl.

Line by line. __init__ stores the two hyperparameters — lr, the step size, and n_iter, how many full passes to take — and leaves the parameters as None because they cannot be sized until we see the data. In fit, X.shape gives us n_samples and n_features, and the weights are initialised to a zero vector of length n_features with the bias at zero; for a convex problem the starting point does not affect the final answer, only how long it takes to get there. Inside the loop, y_pred = X.dot(self.weights) + self.bias is one matrix-vector product that computes all predictions at once. y_pred - y is the residual vector . X.T.dot(y_pred - y) contracts that residual against every feature column simultaneously, giving all partial derivatives in one operation, and dividing by n_samples turns the sum into a mean so the learning rate does not have to be retuned when the dataset size changes. np.sum(y_pred - y) / n_samples is the bias gradient. The two subtraction lines take the downhill step. predict simply reapplies the learned affine map. This is full-batch gradient descent: every iteration touches every row, which is exact but slow on large data; swapping in a random subset per step turns it into stochastic (mini-batch) gradient descent.

The math, and what it buys you.

The loss being minimised is the average of the squared gaps:

The squares matter for two reasons: they make every error positive so overshoots and undershoots do not cancel, and they make a smooth convex bowl in , which means there is exactly one minimum and gradient descent cannot get stuck anywhere else.

Where the gradients come from. Write the residual for row as . Differentiating the MSE with respect to weight and applying the chain rule (the outer derivative of is , the inner derivative of with respect to is ):

Stacked over all , the first expression is exactly — which is the one line of code in fit. The code drops the factor of 2 because it is a constant that just rescales the learning rate — a common and harmless simplification. Notice what the weight gradient says intuitively: a feature gets a large gradient when it is large and correlated with the direction we are currently getting wrong. The bias gradient is just the average residual, which nudges the whole prediction line up or down.

Good and bad.

  • Good: convex, so a global optimum is guaranteed; per step, so it streams over data too big for memory; the same recipe extends unchanged to models with no closed form.
  • Bad: needs feature standardisation — because the same lr is used for every weight, features on wildly different scales converge at wildly different rates and the loss surface becomes a long narrow valley that gradient descent zigzags down.
  • Bad: learning-rate sensitive. Too large and the updates overshoot the minimum and the loss diverges to nan; too small and it crawls.

Follow-up: Why iterate at all when linear regression has a closed-form solution? The normal equation gives the exact optimum in one shot, and for a few thousand features it is the better choice. But it costs roughly to invert (or factorise) a matrix and requires to be invertible, which fails under exact multicollinearity. Gradient descent costs per step, streams over data that does not fit in memory, extends unchanged to models with no closed form, and degrades gracefully when features are collinear.

Why the interviewer asks this. It is the cheapest possible check that you can go from a loss function to its gradient to working vectorised code without reaching for a library.

Saying it out loud. “The model’s just a weighted sum of the features plus an intercept, and I’m scoring it with mean squared error. I initialise the weights at zero, and then each iteration I predict, take the residual — prediction minus truth — and push the weights in the direction that shrinks it. The weight gradient is X transpose times the residual over n, and the bias gradient is just the mean residual. It’s a convex problem, so as long as my learning rate is sane I’ll land at the global optimum. In practice I’d standardise the features first, otherwise one big-scale column dominates the step size.”


Q2: What’s the difference between linear and logistic regression?

In 30 seconds. “They share the same linear core — weights dot features plus a bias. Linear regression uses that number directly as the prediction; logistic regression squashes it through a sigmoid so it comes out as a probability, which means the linear part is really modelling log-odds. And because the output changed, the loss has to change too: MSE on top of a sigmoid loses convexity and kills the gradient exactly when the model is most wrong.”

The short version.

AspectLinear RegressionLogistic Regression
OutputContinuous valuesProbabilities (0-1)
ActivationNone (linear)Sigmoid
Cost FunctionMSELog loss (cross-entropy)
Use CaseRegressionClassification
GradientLinearNon-linear (sigmoid derivative)

Key Difference:

  • Linear: y = w*x + b
  • Logistic: p = sigmoid(w*x + b), then classify p > 0.5

Why it works.

The two models share a linear core, . Linear regression stops there and treats as the prediction. Logistic regression pushes through the sigmoid (also called the logistic function),

which is a smooth S-shaped squash from the whole real line into the open interval , so the output can be read as a probability. Inverting it shows what the linear part really means: , the log-odds (the log of the ratio of the probability of the event to the probability of its complement). So logistic regression is a linear model in log-odds space, not in probability space. A one-unit increase in a feature adds a fixed amount to the log-odds, which multiplies the odds by a constant factor — that is the standard way to interpret a logistic coefficient.

The math, and what it buys you.

Why log loss and not MSE — the part follow-ups probe. There are two independent reasons.

The first is optimisation geometry. With MSE the logistic objective is not convex in , because the sigmoid’s own curvature flips sign at ; the composition can have multiple local minima and flat plateaus, so gradient descent’s answer depends on where it started. Log loss, , is convex in , so there is a single optimum.

The second is gradient behaviour, and it is easy to see numerically. Differentiating MSE through the sigmoid gives a factor of in the gradient. Suppose the true label is but the model is confidently wrong with (so ). Then , and the MSE gradient with respect to is — nearly zero. The model is as wrong as it can be and the update is vanishingly small; it is stuck. Under log loss the term cancels exactly against the derivative of the log, leaving the strikingly simple

which for the same case is : a full-strength correction. The size of the update is proportional to how wrong the probability is, which is exactly what you want. (The same cancellation is why softmax-with-cross-entropy is implemented as a single fused op in every framework.)

Good and bad.

  • Linear regression — good: directly interpretable coefficients in the target’s own units; closed-form solution available. Bad: unbounded output, so it is meaningless as a probability, and MSE punishes it hard on classification labels.
  • Logistic regression — good: calibrated probabilities, convex loss, clean gradient, coefficients readable as odds ratios. Bad: still a linear boundary in feature space; needs feature engineering or kernels for anything curved.

Follow-up: Can you use logistic regression on more than two classes? Yes — replace the sigmoid with the softmax, , one linear score per class, and the loss becomes multiclass cross-entropy. The gradient keeps the same form with as a one-hot vector. This is multinomial logistic regression, also known as softmax regression, and it is precisely the output layer of almost every classification neural network.

Why the interviewer asks this. The surface answer is memorised by everyone; the real question underneath is whether you know why the loss function changes, not just that it does.

Saying it out loud. “They share the same linear core — weights dot features plus a bias. Linear regression uses that number directly as the prediction; logistic regression squashes it through a sigmoid so it comes out as a probability, which means the linear part is really modelling log-odds. And you have to switch the loss too. If you put MSE on top of a sigmoid you lose convexity, and worse, when the model’s confidently wrong the sigmoid derivative kills the gradient so it can’t recover. With log loss all that cancels and the gradient is just predicted minus actual — big error, big update.”


Q3: Explain KNN algorithm.

In 30 seconds. “KNN doesn’t really train — it memorises the data. At prediction time you measure the distance from the new point to everything you stored, take the k closest, and let them vote, or average them for regression. Small k chases noise, big k smooths toward the majority class — that’s the bias-variance dial. Two gotchas: scale your features, and don’t expect it to work in high dimensions.”

The short version.

  • Lazy, non-parametric. Training: O(1), just store. Prediction: O(n) per query, compare to all points.
  • Predict: find the k nearest neighbours → majority vote (classification) or mean (regression).
  • Distance: usually Euclidean ; Manhattan or cosine also used.
  • Small k = sensitive to noise (high variance). Large k = smoother boundary (high bias). Rule of thumb: ; prefer odd for binary problems so votes cannot tie.
  • Must standardise features, and it degrades badly as dimension grows.

Why it works.

K-Nearest Neighbors makes one assumption and nothing else: points that are close together in feature space tend to have similar labels. So there is no model to fit — no weights, no loss function, no gradient. “Training” is memorising the dataset, which is why it is called a lazy learner (all the work is deferred to prediction time) and a non-parametric method (the number of things it remembers grows with the data rather than being fixed in advance).

To predict for a new point , compute the distance from to every stored training point, sort, take the closest, and let them vote. For classification the prediction is the most common label among those ; for regression it is their mean.

import numpy as np
from collections import Counter

class KNN:
    def __init__(self, k=3):
        self.k = k

    def fit(self, X, y):
        # "Training" is just storage - nothing is learned.
        self.X_train = np.asarray(X, dtype=float)
        self.y_train = np.asarray(y)
        return self

    def predict(self, X):
        X = np.asarray(X, dtype=float)
        # Squared Euclidean distance from every test point to every train point.
        # Shape: (n_test, 1, n_features) - (1, n_train, n_features) -> (n_test, n_train)
        d2 = ((X[:, None, :] - self.X_train[None, :, :]) ** 2).sum(axis=2)
        # argpartition puts the k smallest in front in O(n) instead of O(n log n).
        idx = np.argpartition(d2, kth=self.k - 1, axis=1)[:, :self.k]
        neigh = self.y_train[idx]
        return np.array([Counter(row).most_common(1)[0][0] for row in neigh])

Reading it in order: fit casts and stores, and that is genuinely all it does. In predict, the broadcasting trick X[:, None, :] - self.X_train[None, :, :] creates an array of every coordinate-wise difference between every test point and every training point, squaring and summing along the feature axis gives the squared distance matrix. We use squared distance rather than taking the square root because the square root is monotonic — it does not change which neighbours are closest — and skipping it saves time. np.argpartition is used instead of argsort because we only need to know which are smallest, not their internal order, and partitioning is linear rather than . Indexing y_train by those positions gives the neighbours’ labels, and Counter(...).most_common(1) performs the majority vote. For regression the last line becomes neigh.mean(axis=1).

Why controls the bias-variance tradeoff. With every training point is classified perfectly by itself, and the decision boundary wraps tightly around individual points — including mislabelled ones. That is high variance: reshuffle the training set and the boundary moves a lot. As grows, each prediction averages more neighbours, noise cancels, and the boundary smooths; push to and the model always predicts the global majority class, which is maximum bias. Choose by cross-validation.

Good and bad.

  • Good: zero training cost, no assumptions about the decision boundary’s shape, naturally multiclass, and trivially updated by appending new data.
  • Bad — scaling: Euclidean distance sums squared differences across features, so a feature measured in the thousands (say, income) drowns out one measured in single digits (say, number of bedrooms). Standardise before using KNN, always.
  • Bad — curse of dimensionality: as dimension grows, the ratio between the distance to the nearest and the farthest point converges to 1, so “nearest” stops carrying information. KNN is strong in low dimensions and weak in high ones unless you reduce dimensionality first.
  • Bad — inference cost: every query touches the whole training set, and the whole training set must stay in memory.

Follow-up: How do you make prediction faster than per query? Build a spatial index — a KD-tree or ball tree — which prunes whole regions and gives roughly queries in low dimensions, though both degrade to brute force past about 20 dimensions. Beyond that, use approximate nearest neighbour methods (HNSW graphs, IVF or product quantisation as in FAISS), which trade an exact guarantee for orders-of-magnitude speedups; this is the same machinery behind modern vector databases for embedding retrieval.

Why the interviewer asks this. It is a quick probe of whether you understand that some models pay their cost at training time and others at inference time, and whether you remember that distance-based methods need feature scaling.

Saying it out loud. “KNN doesn’t really train — it just stores the data. At prediction time you measure the distance from the new point to everything you stored, grab the k closest, and take a majority vote, or an average if it’s regression. Small k gives you a jagged boundary that chases noise; big k smooths everything out until you’re basically predicting the majority class. Two gotchas I’d always mention: you have to scale your features, because otherwise whichever column has the biggest units owns the distance, and it falls apart in high dimensions because everything ends up roughly equidistant.”


Q4: How does K-means clustering work?

In 30 seconds. “K-means minimises the total squared distance from each point to its cluster’s centre. It alternates two steps — assign every point to the nearest centre, then move each centre to the mean of the points that chose it. Each step is the exact best move given the other, so the objective only goes down and it always converges — but only to a local optimum, which is why you use k-means++ seeding and multiple restarts.”

The short version.

  • Algorithm: initialise centroids → assign each point to nearest centroid → move each centroid to the mean of its members → repeat.
  • Converges when centroids stop moving, or max iterations is hit.
  • Objective: within-cluster sum of squares (inertia). Both steps are exact minimisers, so inertia is monotonically non-increasing.
  • Initialisation matters: random seeding can give poor results; k-means++ spreads the initial centres out.
  • Limitations: assumes spherical, similarly-sized clusters; you must specify ; sensitive to initialisation and outliers.

Why it works.

K-means partitions points into groups by minimising the within-cluster sum of squares, also called inertia. Minimising this jointly over both the assignments and the centroids is NP-hard, so the standard algorithm — Lloyd’s algorithm — alternates between optimising one while holding the other fixed. That is the whole trick, and it is why each of the two steps in the loop is not arbitrary but is the exact minimiser given the other.

Holding centroids fixed, the assignment that minimises the objective is obviously “put each point with its nearest centroid” — each point’s contribution is minimised independently. Holding assignments fixed, the point minimising is the arithmetic mean of (set the derivative to zero and solve). Since each step can only decrease the objective and there are finitely many possible assignments, it is monotonically non-increasing and the algorithm must terminate — but only at a local minimum, which is exactly why initialisation matters.

The math, and what it buys you.

The objective is a single line, and everything about the algorithm’s behaviour follows from it:

where is the set of points assigned to cluster and is that cluster’s centroid (its mean vector). Because uses squared Euclidean distance and summarises each cluster by a single mean, the algorithm is implicitly committed to round, equally-sized, outlier-free clusters — the limitations are not bugs, they are what this formula asks for.

import numpy as np

def kmeans(X, k, n_iter=100, tol=1e-6, seed=0):
    rng = np.random.default_rng(seed)
    X = np.asarray(X, dtype=float)
    n = X.shape[0]

    # --- k-means++ seeding: spread the initial centroids out ---
    centers = [X[rng.integers(n)]]
    for _ in range(k - 1):
        d2 = np.min(((X[:, None, :] - np.array(centers)[None, :, :]) ** 2).sum(2), axis=1)
        probs = d2 / d2.sum()                      # far-from-everything points are likelier
        centers.append(X[rng.choice(n, p=probs)])
    centers = np.array(centers)

    for _ in range(n_iter):
        # Assignment step: nearest centroid for every point
        d2 = ((X[:, None, :] - centers[None, :, :]) ** 2).sum(axis=2)
        labels = d2.argmin(axis=1)
        # Update step: centroid = mean of its members (keep old center if empty)
        new_centers = np.array([X[labels == j].mean(axis=0) if np.any(labels == j)
                                else centers[j] for j in range(k)])
        if np.linalg.norm(new_centers - centers) < tol:
            centers = new_centers
            break
        centers = new_centers

    inertia = ((X - centers[labels]) ** 2).sum()
    return centers, labels, inertia

Step by step: the seeding block implements k-means++, which picks the first centre uniformly at random and then picks each subsequent centre with probability proportional to its squared distance from the nearest already-chosen centre. That biases the initial centres to be far apart, which avoids the classic failure where two centres land inside the same true cluster and split it while merging two others; it also comes with a proof that the expected inertia is within of optimal. Inside the main loop, the distance matrix is built by the same broadcasting pattern as KNN, argmin along the centroid axis performs the assignment, and the list comprehension recomputes each centroid as the mean of its members — with a guard, because a cluster can end up empty and mean of an empty slice is nan. The convergence check compares how far the centres moved; when that is below tolerance nothing more will change. inertia is the objective value, useful for comparing runs.

Choosing . Inertia decreases monotonically with (at it is zero), so you cannot just minimise it. The elbow method plots inertia against and looks for the bend where extra clusters stop buying much; the silhouette score, which compares each point’s mean distance to its own cluster against its mean distance to the nearest other cluster, gives a value in that can be maximised directly and is usually the more defensible choice.

Good and bad.

  • Good: per iteration — linear in the number of points, so it scales to large data where hierarchical clustering ( memory at least) does not. Simple, and guaranteed to converge.
  • Bad — shape assumption: it will happily slice a long thin cluster in half or merge two crescents. DBSCAN or a Gaussian mixture handles elongated or nested shapes.
  • Bad — outliers: squared distance means one far-away point drags a centroid toward it. K-medoids, which uses actual data points as centres, is the robust alternative.
  • Bad — local optima: standard practice is n_init restarts keeping the lowest-inertia run; scikit-learn does this by default.

Follow-up: What is the time complexity? Each iteration is — every point against every centroid in dimensions — for iterations and restarts, so overall. It is linear in the number of points, which is why K-means scales to large data where hierarchical clustering ( memory at least) does not.

Why the interviewer asks this. They want to hear that the two steps are coordinate descent on a specific objective, not a heuristic someone made up — and that you know why it only ever finds a local optimum.

Saying it out loud. “K-means is trying to minimise the total squared distance from each point to its cluster’s centre. It alternates two steps: assign every point to the nearest centre, then move each centre to the mean of the points that picked it. Each step is the exact best move given the other one, so the objective only goes down and it has to converge — but only to a local optimum, which is why you use k-means++ seeding and multiple restarts. It assumes round, similarly-sized clusters, so if the real shapes are elongated or nested, DBSCAN or a Gaussian mixture is a better fit.”


LLM Fundamentals

Q5: Explain the transformer architecture.

In 30 seconds. “Every token carries a vector up through the stack, and each layer reads that vector, computes something, and adds its result back — that’s the residual stream. Inside a block there are exactly two jobs: attention moves information between positions, and the feed-forward net adds nonlinear capacity within a position. LayerNorm keeps the scale under control, positional information has to be injected because attention itself is order-blind, and modern LLMs are decoder-only with a causal mask, pre-norm, and rotary positions.”

The short version.

Components:

  1. Embedding Layer: Token → Dense vectors
  2. Position Encoding: Add position info
  3. Transformer Blocks (N layers):
    • Multi-Head Self-Attention
    • Feed-Forward Network
    • Layer Normalization
    • Residual Connections
  4. Output Layer: Project to vocabulary

Key Innovation:

  • Self-attention: Relate all positions
  • Parallel processing: All positions at once
  • Long-range dependencies: No RNN limitations

The three configurations, side by side.

ConfigurationAttentionTrained byUsed forExamples
Encoder-decoderBidirectional encoder + causal decoder with cross-attentionSeq2seqTranslation, the original 2017 designT5, original Transformer
Encoder-onlyBidirectionalMasked-token predictionUnderstanding: classification, retrieval. Cannot generate autoregressivelyBERT
Decoder-onlyCausal ( sees only )Next-token predictionEverything, via prompting — the dominant LLM designGPT, Llama, Claude

Why it works.

The clearest mental model of a transformer is the residual stream. Every token carries a vector of width from the bottom of the network to the top, and each sublayer reads from that vector, computes something, and adds the result back. That is what the residual connection means: the block computes , not . Because the update is additive, gradients flow to the bottom layer along an unobstructed path (the derivative of has an identity term in it), which is what makes stacking 100 blocks trainable at all.

Within a block the two sublayers have complementary jobs. Attention moves information between token positions — it is the only operation in the whole architecture that lets position see position . The feed-forward network (FFN) processes each position independently, applying the same two-layer MLP to every token separately: it projects up to an inner width (classically ), applies a nonlinearity such as GELU or SwiGLU, and projects back down. Attention mixes across tokens; the FFN adds nonlinear capacity within a token. Note that the FFN holds roughly two-thirds of the parameters in a standard block ( versus attention’s ), which is why it is the usual target for mixture-of-experts sparsification.

Layer normalisation rescales each token’s vector to zero mean and unit variance across its features (then applies a learned gain and bias), which keeps activation magnitudes stable as depth grows. The original 2017 paper put it after the residual addition (post-LN); essentially every modern LLM puts it before the sublayer (pre-LN), because post-LN needs a learning-rate warmup to train deep stacks without diverging while pre-LN is stable out of the box. Many current models further simplify to RMSNorm, which divides by the root-mean-square and skips the mean subtraction and the bias.

Position encoding is needed because attention is permutation-equivariant: with no positional signal, “dog bites man” and “man bites dog” produce identical sets of representations. The original design added fixed sinusoids of geometrically spaced frequencies to the embeddings; current models overwhelmingly use RoPE (rotary position embedding), which rotates the query and key vectors by an angle proportional to position so that the attention score depends only on the relative offset between two tokens — that relative property is what makes context-length extension via frequency scaling possible.

Three configurations, in prose. The original paper is an encoder-decoder, built for translation: a bidirectional encoder reads the source, and a decoder generates the target while cross-attending to the encoder’s output. Encoder-only models (BERT) keep bidirectional attention and are trained by masked-token prediction; they are for understanding tasks like classification and retrieval, and cannot generate autoregressively. Decoder-only models (GPT, Llama, Claude) use causal masking — position may attend only to positions — and are trained to predict the next token; this is the dominant design for LLMs today because a single next-token objective scales cleanly and covers generation, classification, and everything else via prompting.

Good and bad.

  • Good: all positions are processed in parallel during training (no RNN recurrence), long-range dependencies are one hop away rather than hops, and the additive residual stream makes very deep stacks trainable.
  • Bad: attention is in sequence length; there is no built-in notion of order, so positions must be injected; and the parameter count is dominated by the FFN, which is mostly idle capacity for any given token.

Follow-up: Where does the quadratic cost come from, and what is done about it? The attention score matrix is for sequence length , so compute is and, naively, memory is too. FlashAttention removes the memory term by tiling the computation and never materialising the full matrix — it is exact, just IO-aware. The compute term is attacked by sparse or sliding-window attention (attend only to a local neighbourhood plus a few global tokens) and by linear-attention or state-space alternatives such as Mamba, which trade some expressivity for scaling.

Why the interviewer asks this. It is an open door: they want to see which level of detail you naturally reach for, and whether you can say what each component is for rather than just listing the diagram top to bottom.

Saying it out loud. “The way I picture it, every token carries a vector up through the stack, and each layer reads that vector, computes something, and adds its result back — that’s the residual stream. Inside a block there are two jobs. Attention is the only thing that moves information between positions. The feed-forward net works on each token on its own and gives you the nonlinear capacity — it’s also where most of the parameters live. LayerNorm keeps the scale under control, and you need positional information injected somewhere because attention on its own has no idea what order the tokens came in. Modern LLMs are decoder-only with a causal mask, pre-norm, and rotary positions.”


Q6: How does self-attention work?

In 30 seconds. “Each token projects into a query, a key, and a value. The query dotted with every key scores relevance, you divide by root d-k so the softmax doesn’t saturate, softmax turns the scores into weights that sum to one, and the output is a weighted average of the values. The clean way to say it: query-key decides where you look, value-output decides what gets copied — it’s a soft, content-addressable lookup.”

The short version.

Formula:

Attention(Q, K, V) = softmax(QK^T / √d_k) × V

Steps:

  1. Compute Q, K, V from input
  2. Compute attention scores: Q @ K^T
  3. Scale by √d_k (prevent large values)
  4. Softmax to get attention weights
  5. Apply weights to V
RoleQuestion it answersCircuit it belongs to
Query“What am I looking for?”QK — where to look
Key“What information do I have?”QK — where to look
Value“The actual information”OV — what to move
Attention weight“How relevant is that token to me?”Output of softmax over QK

Why it works.

Start from the projections. Each token’s residual-stream vector is multiplied by three learned matrices to give a query , a key , and a value . The useful way to read this is that attention factors into two independent circuits:

  • The QK circuit decides where to look. The score between positions and is , so only the product matters — a single bilinear form saying “how much does a token like me want to read from a token like that?” This circuit produces the attention pattern and nothing else; it never touches the content that gets copied.
  • The OV circuit decides what to move. Once the weights are fixed, the output is , so only the product matters — a linear map saying “if I read from a token, what do I write into my residual stream?”

Splitting it this way explains attention’s real function: it is a soft, content-addressable lookup. The QK circuit computes addresses, the softmax turns them into a normalised mixing weight, and the OV circuit is the payload. A concrete example is the induction head found in real models: the QK circuit matches the current token against earlier occurrences of the same token, and the OV circuit copies whatever followed it last time, which is how models do in-context pattern completion.

Causal masking. In a decoder, position must not see the future, or the model could cheat at next-token prediction. This is implemented by adding (in practice a large negative number like ) to all scores where before the softmax, so those weights come out as exactly zero after exponentiation. Doing it pre-softmax rather than zeroing afterwards matters, because it keeps the remaining weights correctly normalised to sum to one.

The math, and what it buys you.

Why divide by — with numbers. The scaling factor is not decoration; it is what keeps the softmax’s gradient alive. Suppose the entries of and are roughly independent with mean 0 and variance 1. Their dot product is a sum of such products, so it has variance and typical magnitude . With that is around ; with , around . Feed scores that large into a softmax and it saturates — one weight goes to essentially 1 and the rest to essentially 0. Concretely, softmax over gives , and the gradient of the softmax is proportional to , so at the gradient is about : the attention pattern freezes and stops learning. Dividing by renormalises the scores to unit variance regardless of head width, so the softmax stays in its responsive range. Softmax over gives with a healthy gradient.

A small worked example. Take and three tokens with and keys , , . The raw scores for query 2 are . Scaling by gives . Exponentiating gives , which sums to , so the attention weights are . The output for position 2 is — a convex combination of the value vectors, weighted by query-key similarity. Every row of the attention matrix is a probability distribution over positions in exactly this way.

Follow-up: Why do we need three separate projections — why not just use itself? Because is maximised by itself, so unprojected attention would collapse to every token attending mostly to itself, and the score would be forced to be symmetric — position would attend to exactly as much as attends to . Separate and break that symmetry and let “what I’m looking for” differ from “what I advertise”. A separate then decouples relevance from content, so a token can be highly relevant as an address while contributing something entirely different as a payload.

Why the interviewer asks this. They want to know whether “query, key, value” is a phrase you repeat or a structure you can decompose — the split between where attention looks and what it copies is the tell.

Saying it out loud. “Each token projects into three vectors — a query, a key, and a value. The query is what this token is looking for, the key is what each other token advertises, and the dot product between them scores relevance. You divide by root d-k because otherwise the dot products grow with the head dimension, the softmax saturates, and the gradient dies. Then softmax turns those scores into weights that sum to one, and the output is a weighted average of the value vectors. So really there are two separate circuits: query-key decides where you look, and value-output decides what gets copied back into your representation. In a decoder you mask out the future before the softmax so a token can’t see what comes after it.”


Q7: What is multi-head attention?

In 30 seconds. “Multi-head attention splits the model dimension across heads rather than duplicating it — 512 dimensions and 8 heads means each head works in 64, so the parameter count is identical to one wide head. What you buy is several attention distributions running in parallel instead of one. A single softmax has to spend all its probability mass in one place; eight heads can do eight different lookups at once.”

The short version.

  • Concept: instead of one attention, use multiple heads; each learns different relationships; concatenate, then project.
  • Implementation: split into num_heads × → each head gets its own Q, K, V slice → attention per head → concatenate → final projection .
  • Parameters are partitioned, not multiplied: total is , the same as a single full-width head.
  • Why it helps: different heads attend to different aspects — one for syntax, one for semantics — which one distribution cannot do.
  • The modern wrinkle: KV cache scales with head count at inference, hence MQA and GQA.

Why it works.

A single head produces one attention distribution per query position — it must commit to one weighted average. That is a hard constraint: a pronoun resolving its antecedent and a verb finding its subject are different lookups that a single softmax cannot perform at once, because probability mass spent on one is taken from the other. Eight heads give eight independent distributions whose results are summed into the residual stream, so the block can perform several distinct retrievals in parallel. The cost is that each head sees a lower-rank slice of the space, which is a real limitation — this is why very small (below about 32) tends to hurt, and why head count and head dimension are tuned together rather than head count alone being maximised.

The math, and what it buys you.

The dimension arithmetic is the thing to be able to recite, because it shows the heads are free. With and heads, each head gets . In practice you keep one big matrix for (and likewise , ), project once, then reshape the result from to and transpose to so the eight heads become a batch dimension. Every head runs the scaled-dot-product attention of Q6 independently on its own 64-dimensional slice, producing . Transposing back and reshaping concatenates the heads into , and the output projection (also ) mixes them before the result is added to the residual stream. Total parameters: , identical to one head of full width — so multi-head attention buys parallel lookups at zero parameter cost.

Good and bad.

  • Good: several independent retrievals per layer for the same parameter budget; heads specialise (positional heads, induction heads, syntax heads) and are individually interpretable.
  • Bad: each head operates in a lower-rank subspace, so pushing below roughly 32 degrades quality; and head count directly inflates the inference KV cache, which is why MQA and GQA exist.

Follow-up: What are MQA and GQA, and why do they exist? The KV cache at inference stores one key and one value vector per head per token, so its size scales with head count — and at generation time the arithmetic is memory-bandwidth-bound, meaning the GPU spends most of its time reading that cache rather than doing math. Multi-query attention (MQA) keeps separate query heads but shares a single key/value head across all of them, shrinking the cache by a factor of ; it is fast but measurably degrades quality. Grouped-query attention (GQA) is the compromise now used by most open models: heads are split into groups (say 8 groups over 64 query heads) with one KV head per group, recovering most of the quality at most of the speed. Nothing about the query side changes; only the number of distinct K and V projections does.

Why the interviewer asks this. The giveaway answer is “heads learn different things”, which is true but unfalsifiable; they are checking whether you know the dimensions split rather than multiply, and ideally that you know why inference pushed the field toward GQA.

Saying it out loud. “Multi-head attention splits the model dimension across heads rather than duplicating it — 512 dimensions and 8 heads means each head works in 64. So the parameter count is the same as one wide head, but instead of one attention distribution you get eight running in parallel, and you concatenate them and pass them through an output projection. The reason that matters is that a single softmax has to spend its probability mass in one place; with several heads the layer can do several different lookups at once. The modern wrinkle is that KV cache size scales with head count at inference, so people share key and value heads across groups of query heads — that’s grouped-query attention.”


LLM Inference

Q8: How does KV caching work?

In 30 seconds. “Because of the causal mask, once you’ve computed a token’s key and value they never change — nothing later can affect them. So instead of recomputing the whole sequence every step, you keep K and V and compute only the query for the new token. It turns generation from cubic in sequence length to quadratic, exactly, with no quality trade. The catch is memory: roughly half a megabyte per token on a 7B model.”

The short version.

  • Problem: autoregressive generation recomputes attention over all previous tokens at every step.
  • Solution: cache K and V for previous tokens; for the new token compute only Q, reuse cached K/V, append the new K/V.
  • Why only K and V: old queries are never used again; the one new query must be scored against every previous key and value.
  • Exact, not approximate. Same outputs, up to float reduction order.
  • Speedup: 10-100x for generation. Cost: linear-in-context GPU memory.
Step 1: Token 1 → Compute Q1, K1, V1, cache K1, V1
Step 2: Token 2 → Compute Q2, K2, V2
        → Attention: Q2 @ [K1, K2]^T, use [V1, V2]
        → Cache: [K1, K2], [V1, V2]
Step 3: Token 3 → Compute Q3, K3, V3
        → Attention: Q3 @ [K1, K2, K3]^T, use [V1, V2, V3]
        → Cache: [K1, K2, K3], [V1, V2, V3]

Why it works.

The reason caching is even possible is causal masking. In a decoder, position attends only to positions , so once token ’s key and value vectors are computed they are final — no later token can change them, because nothing later flows into them. That is a property of the mask, not an approximation; KV caching is mathematically exact, producing bit-comparable outputs (up to floating-point reduction order), not a speed-for-quality trade.

Why keys and values but not queries: at each generation step you have exactly one new token, so you need exactly one new query — the queries of earlier tokens are never used again, because their outputs were already computed and consumed. But that single new query must be scored against every previous key and must read from every previous value. So K and V accumulate; Q does not.

The math, and what it buys you.

What this does to the cost. Without a cache, generating token means a full forward pass over tokens, which is of attention work, and generating a whole sequence of length costs . With the cache, each step is one query against keys, so per step and overall — a factor of saved. This is also why LLM inference has two distinct phases with very different characteristics: prefill, where the whole prompt is processed in one parallel pass and the GPU is compute-bound, and decode, where one token is produced at a time and the GPU is memory-bandwidth-bound because it must stream the entire cache and all model weights per token.

The cost is memory, and this formula is the one to know — it tells you how many concurrent requests a GPU can hold:

The leading 2 is for K and V. Take a 7B-class model with 32 layers, 32 KV heads, head dimension 128, in FP16 (2 bytes): that is bytes per token, or about 0.5 MB. At 4,096 tokens of context that is roughly 2 GB for a single sequence — and it scales linearly with batch size, so 16 concurrent requests at that length is about 32 GB, comfortably more than the model weights themselves. This is the single biggest constraint on serving throughput, and it is what motivates GQA (fewer KV heads), KV-cache quantisation to INT8 or FP8, and PagedAttention in vLLM, which stores the cache in fixed-size non-contiguous blocks like OS virtual memory so that fragmentation and over-reservation stop wasting the majority of GPU memory.

Good and bad.

  • Good: exact, no quality cost; removes a whole factor of from generation; enables prefix caching across requests that share a system prompt.
  • Bad: memory grows linearly with context and batch, and quickly exceeds the weights themselves; fragmentation wastes more of it unless you use a paged allocator; and any change at the front of a prompt invalidates everything after it.

Follow-up: What breaks if the prompt changes at the front? Everything downstream of the change, because each cached key depends on all preceding tokens through the attention of earlier layers. This is why prefix caching works — a shared system prompt at the start can be computed once and reused across requests — but appending to the beginning of a prompt invalidates the whole cache. It is a concrete reason to put stable content first and variable content last in prompt templates.

Why the interviewer asks this. It separates people who have read about transformers from people who have served them; the memory formula and the prefill/decode distinction are what a serving engineer actually reasons about.

Saying it out loud. “Because of the causal mask, once you’ve computed a token’s key and value they never change — nothing later can affect them. So instead of recomputing the whole sequence every step, you keep K and V around and just compute the query for the new token. You don’t cache queries because old queries are never used again. It turns generation from cubic in sequence length to quadratic. The catch is memory: the cache is roughly half a megabyte per token on a 7B model, so at long context and decent batch sizes it gets bigger than the weights — that’s why people use grouped-query attention, quantise the cache, and use paged allocators like vLLM.”


Q9: What is quantization and why use it?

In 30 seconds. “Quantisation stores weights as low-bit integers plus a scale, so you can reconstruct an approximate float. The accuracy cost is just bounded rounding error — what actually hurts is outliers, because one huge weight blows up the scale for everything sharing it. And the win is bigger than the arithmetic suggests, because decoding is memory-bandwidth-bound: halving the bytes roughly halves latency before you touch the math.”

The short version.

  • Each halving of precision is roughly 2x smaller and 2x faster: FP32 → FP16 → INT8 → INT4.
  • Why: memory (bigger models fit), speed (bandwidth-bound decode), cost (cheaper inference).
  • Process: find min/max of the weights → compute a scale factor → round to the integer range → store the scale for dequantisation.
  • Trade-off: small accuracy loss; INT8/INT4 need calibration.
  • The real enemy is outliers, not average precision — hence group-wise scales.
Bit widthTypical quality costNotes
FP16NoneThe baseline for serving
INT8Near-lossless on quality benchmarksCalibration needed for activations
INT4 (grouped)Small, and uneven — reasoning, arithmetic and rare languages degrade firstGPTQ / AWQ / NF4 territory
Below 4-bitFalls off sharplyNeeds specialised methods

Why it works.

Quantisation replaces a high-precision tensor with low-precision integers plus a small amount of metadata to reconstruct approximate floats.

Why the speedup is bigger than the arithmetic suggests. During single-token decoding the GPU is memory-bandwidth-bound: it must read every weight from HBM to produce one token, and the matrix multiplies are small. Halving the bytes per weight therefore roughly halves the time per token even if the arithmetic itself runs at the same rate. This is why weight-only quantisation — store INT4, dequantise to FP16 in the kernel, multiply in FP16 — is so popular for LLM serving: it captures the bandwidth win without needing integer matmul support or activation calibration.

The families worth naming. Post-training quantisation (PTQ) converts an already-trained model, optionally using a few hundred calibration samples to set activation ranges; GPTQ (which uses second-order information to compensate rounding error weight by weight) and AWQ (which scales up the channels the activations actually depend on before quantising) are the standard 4-bit PTQ methods. Quantisation-aware training (QAT) simulates rounding during training with a straight-through estimator for the gradient, costing a training run but retaining more accuracy at very low bit widths. QLoRA is the hybrid people actually use for fine-tuning: freeze a 4-bit NF4 base model and train small LoRA adapters in higher precision on top.

The math, and what it buys you.

The whole scheme is one affine map between floats and integers. The standard asymmetric (affine) scheme picks a scale and a zero-point so that

with and chosen so that real zero maps exactly to an integer (which matters, because padding and ReLU outputs produce a lot of exact zeros and you do not want them to drift). The symmetric variant fixes and uses ; it is cheaper because the dequantisation has no offset term, and it is the usual choice for weights, which are roughly zero-centred.

A worked example — and it shows exactly where the danger is. Suppose a weight block ranges over and we quantise to INT8, which covers . Asymmetrically, . A weight of maps to (plus the zero-point offset), and dequantising gives — an error of about 0.0018, roughly half a step of . That bounded round-off is the entire accuracy cost. Now note what happens with one outlier: if a single weight in the block were , the scale would jump to and every ordinary weight would carry 27 times more error. Outliers, not average precision, are what actually break quantisation — which is why modern methods quantise in small groups (say 64 or 128 weights sharing one scale) rather than per-tensor, and why LLM.int8() keeps a handful of outlier channels in FP16 while quantising the rest.

Good and bad.

  • Good: proportional memory savings, proportional latency savings during bandwidth-bound decode, and lower serving cost; INT8 is effectively free in quality terms.
  • Bad — the honest trade-off: INT4 with good grouping loses a little, and it loses it unevenly — long-chain reasoning, arithmetic, and rarely-seen languages degrade before general fluency does, so a perplexity check alone can look fine while a task benchmark drops. Below 4 bits quality falls off sharply without specialised methods.
  • Bad: activation quantisation needs calibration data and can be exceeded at run time; outlier channels make naive per-tensor scales lossy.

Follow-up: Why not just quantise activations too? You can, and INT8 activations are what let you use integer tensor cores for a genuine compute win. But activations depend on the input, so their range must be estimated from calibration data and can be exceeded at run time, and transformer activations contain systematic large-magnitude outlier channels that make per-tensor activation scales very lossy. That is exactly the problem LLM.int8() and SmoothQuant (which migrates the difficulty from activations into weights by rescaling) were designed to solve.

Why the interviewer asks this. They want to know if you can reason about the deployment constraint — memory bandwidth — rather than reciting bit widths.

Saying it out loud. “Quantisation is storing weights as low-bit integers plus a scale, so you can reconstruct an approximate float. The accuracy cost is just rounding error, bounded by half a step of the scale — the thing that actually hurts is outliers, because one huge weight blows up the scale for everything sharing it. That’s why people quantise in small groups instead of per-tensor. And the reason it’s such a big win for LLMs is that decoding is memory-bandwidth-bound: you’re reading every weight from memory to emit one token, so halving the bytes roughly halves your latency even before you touch the arithmetic. INT8 is basically free, INT4 with good grouping is usually acceptable, and below that you need real work.”


Q10: Explain top-p (nucleus) sampling.

In 30 seconds. “Top-p keeps the smallest set of tokens whose probabilities add up to p — say 0.9 — and samples from that after renormalising. The point is that the set size adapts: when the model is confident that’s one token and you’re effectively greedy; when it’s genuinely uncertain it might be hundreds. Fixed top-k can’t do either — it lets junk in when the model is sure and cuts off good options when it isn’t.”

The short version.

Algorithm:

  1. Sort tokens by probability (descending)
  2. Compute cumulative probability
  3. Find smallest set where cum_prob >= p
  4. Sample from this “nucleus”
  5. Renormalize probabilities
Top-kTop-p (nucleus)
What is fixedThe number of tokensThe probability mass kept
What floatsThe mass keptThe number of tokens
Peaked distributionAdmits tokens that should be ruled outCollapses to ~1 token, effectively greedy
Flat distributionTruncates hundreds of legitimate optionsWidens to include them
Probabilities: [0.5, 0.3, 0.1, 0.05, 0.03, ...]
Cumulative:    [0.5, 0.8, 0.9, 0.95, 0.98, ...]
Top-p=0.9: Nucleus = first 3 tokens (cum_prob = 0.9)

Why it works.

The problem top-p solves is that a language model’s next-token distribution has wildly varying shape from step to step. After “the capital of France is” the distribution is nearly a spike — one token holds most of the mass. After “she opened the door and saw a” it is broad, with hundreds of plausible continuations. Top-k with a fixed handles neither well: at the spike it admits tokens that should have been ruled out, and at the broad step it truncates hundreds of legitimate options. Top-p instead fixes the probability mass to keep and lets the count float, so the nucleus is 1 token in the first case and several hundred in the second. That is the entire argument for it.

What it is actually fixing. The tail of a softmax over a 100k-token vocabulary contains tens of thousands of tokens each with tiny probability, but their sum can be a few percent. Sample long enough and you will draw from that tail, and one bad token conditions everything after it — the model has no way to take it back, and degeneration into incoherence follows. Truncation sampling exists because the model’s tail is less trustworthy than its head, not because the tail has zero mass.

How it composes with temperature. Temperature rescales the logits before the softmax, . Below 1 it sharpens the distribution and above 1 it flattens it. Order matters: temperature is applied first, then top-k/top-p truncation, then renormalisation. So raising temperature does not only make sampling more random — it also enlarges the nucleus, because flattening the distribution means more tokens are needed to accumulate mass . The two knobs interact, which is why tuning both at once tends to be confusing and why most practitioners fix one (commonly with , or with a tuned temperature).

The math, and what it buys you.

Running the numbers on two different steps shows the adaptivity directly. Take probabilities over six tokens, with . The cumulative sums are . The smallest prefix reaching 0.9 is the first three, so the nucleus is , summing to 0.9, and after renormalising (dividing by 0.9) we sample from . Now take the peaked case : the first token alone already reaches 0.95 which is , so the nucleus is a single token and generation is effectively greedy at that step. Same , completely different set size — which is exactly what you want. (Note the standard convention: the nucleus is the smallest prefix whose cumulative probability is at least , so the kept mass is always , never less.)

Good and bad.

  • Top-p — good: adapts to the model’s confidence step by step; cuts the untrustworthy tail without capping variety when variety is warranted; one intuitive knob.
  • Top-p — bad: interacts confusingly with temperature; a single is still a blunt global setting; and it does nothing for a model whose head of the distribution is simply wrong.
  • Top-k — good: trivially cheap and bounded. Bad: fixed set size is wrong at both ends of the confidence range.

Follow-up: When would you not want to sample at all? Whenever there is a single correct answer and diversity is a liability: extraction, classification, structured or JSON output, most tool-call arguments, and any evaluation you want to be reproducible. There greedy decoding (, equivalently top-k of 1) is right. Sampling is for open-ended generation where you want variety across runs. A common middle ground for reasoning tasks is to sample several times at moderate temperature and take a majority vote over final answers — self-consistency — which uses diversity as a search strategy rather than as an end in itself.

Why the interviewer asks this. Decoding parameters are the knobs everyone touches and few can explain; being able to say precisely why the adaptive cutoff beats a fixed one is a small but reliable signal.

Saying it out loud. “Top-p keeps the smallest set of tokens whose probabilities add up to p — say 0.9 — and samples from that after renormalising. The point is that the set size adapts. When the model’s confident, that’s one or two tokens and you’re basically greedy; when it’s genuinely uncertain, it might be hundreds. Fixed top-k can’t do that: it lets junk in when the model is sure and cuts off good options when it isn’t. And it composes with temperature — temperature is applied first, so cranking it up doesn’t just add randomness, it also widens the nucleus.”


Training Techniques

Q11: Explain RLHF (Reinforcement Learning from Human Feedback).

In 30 seconds. “RLHF is three stages: fine-tune on human demonstrations to teach the model what a response looks like, train a reward model on preference pairs because people compare far better than they score, then run RL — usually PPO — to push the policy toward higher reward with a KL leash back to the SFT model. The fragile stage is the reward model: the policy actively hunts its blind spots, so true quality peaks and then declines while the score keeps rising.”

The short version.

StageWhat it trains onWhat it actually teachesFailure mode
1. Supervised fine-tuningHuman-written prompt-response pairsFormat — that a question gets answeredBland, imitates the demonstrator’s ceiling
2. Reward modelPreference pairs (chosen vs rejected)A scalar proxy for human judgementOnly valid near its training distribution
3. RL optimisation (PPO)Policy samples scored by the RMHigher expected reward, KL-constrainedReward hacking; needs 4 models in memory
  • Why RLHF: align models with human preferences; helpful, harmless, honest; better response quality.
  • Challenges: collecting human feedback, training the reward model, RL complexity.

Why it works.

Stage 1, supervised fine-tuning (SFT). Start from a pretrained base model, which can only continue text, and train it on human-written prompt-response pairs with ordinary next-token cross-entropy. This does not teach new knowledge; it teaches format — that a question should be answered rather than continued with more questions. It also matters technically, because the RL stage needs a starting policy that already produces plausible outputs; RL from a raw base model would spend its entire budget rediscovering the response format.

Stage 2, the reward model (RM). Humans are unreliable at assigning absolute scores (“how good is this answer out of 10?”) but quite reliable at comparisons (“which of these two is better?”), so the data collected is preference pairs.

Stage 3, RL optimisation. The policy generates responses, the RM scores them, and PPO updates the policy to raise expected reward while a KL penalty against the frozen SFT model keeps it from wandering.

Why the reward model is the weak link. It is trained on a finite sample of on-distribution responses, but the policy immediately starts producing responses the RM never saw. Since the policy is explicitly optimising the RM’s output, it will find and exploit whatever regions the RM overestimates — this is Goodhart’s law in its purest engineering form. Empirically, measured true quality rises, peaks, and then falls while the RM’s score keeps climbing. The standard mitigations are the KL penalty (Q17), early stopping on the KL budget rather than on RM score, and periodically collecting fresh preferences on the current policy’s outputs, which is what “iterated RLHF” means.

The math, and what it buys you.

The reward model is usually the SFT model with the token-prediction head replaced by a scalar head, trained with the Bradley-Terry loss:

where is the preferred (“won”) response, the rejected one, and the sigmoid. Note what this objective does and does not pin down: it constrains differences in reward, so the scale is arbitrary and the absolute value of a reward is meaningless — only comparisons within a prompt are trustworthy. The model implicitly assumes preferences follow the Bradley-Terry model, that the probability a human prefers is .

The RL stage then optimises a per-token objective that is reward minus drift:

The reward is sparse and terminal — one scalar for the whole response — which is why a value model is needed to spread credit back over the tokens, and why this stage is the fragile one.

Good and bad.

  • Good: captures preferences that cannot be written down as a loss; can exceed the quality of the demonstrations, because it optimises judgement rather than imitation.
  • Bad: expensive human data; three stages of machinery and up to four models in GPU memory; and the learned reward is hackable by construction.

Follow-up: What replaced parts of this pipeline? Two things, mostly. RLAIF / Constitutional AI replaces human labels with model-generated preferences guided by a written set of principles, which makes the preference data far cheaper to scale. And for anything with a checkable answer, RLVR — reinforcement learning from verifiable rewards — replaces the learned reward model with a program that checks correctness, which removes the hackable component entirely. DPO (Q12) removes the RL loop while keeping human preferences.

Why the interviewer asks this. They are checking that you know why there are three stages rather than one, and ideally that you can name which stage actually breaks in practice.

Saying it out loud. “It’s three stages. First you fine-tune on human-written demonstrations, which mostly teaches the model what a response is supposed to look like. Then you collect preference pairs — people are much better at saying which of two answers is better than at scoring one in isolation — and train a reward model on those comparisons. Then you run RL, usually PPO, to push the policy toward higher reward, with a KL penalty back to the SFT model so it doesn’t drift off. The fragile bit is the reward model: the policy is actively searching for its blind spots, so true quality tends to peak and then decline while the reward score keeps going up.”


Q12: What is DPO and how does it differ from RLHF?

In 30 seconds. “DPO comes from noticing that the KL-constrained RLHF objective has a closed-form optimal policy. Invert it, and the reward can be written as the policy’s log-ratio against the reference; plug that into the preference likelihood and the intractable normaliser cancels. So you get a plain supervised loss on preference pairs — no reward model, no sampling, no RL loop. The cost is that it’s off-policy: it only ever sees the pairs in your dataset.”

The short version.

AspectRLHFDPO
Reward ModelYesNo
Reference ModelUsed in RLUsed directly
ComplexityHighLower
FlexibilityMoreLess
DataOnline — fresh samples from the current policyOff-policy — a fixed dataset of pairs
Models in memory3-41 (reference log-probs precomputed)
  • Directly optimises the policy to prefer chosen over rejected.
  • No reward model needed; uses the reference model instead.

DPO Loss:

Loss = -log(σ(β * (log π_chosen - log π_rejected - log π_ref_chosen + log π_ref_rejected)))

Where σ is sigmoid, β is temperature.

Why it works.

The insight is that the language model is its own reward model — the log-ratio to the reference is an implicit reward — so you never have to instantiate a separate one, and you never have to sample from the policy during training. DPO is a supervised loss on a fixed dataset of pairs.

What the gradient does. Differentiating gives a weight of on each pair: the update is large when the implicit reward currently ranks the pair wrongly and small when it already ranks it correctly. So DPO automatically focuses on the examples it has not yet learned, which is the same self-limiting behaviour that makes the logistic loss well-behaved.

The math, and what it buys you.

This derivation is the whole answer — it turns DPO from a heuristic into a consequence. The KL-constrained RLHF objective has a known closed-form optimum. If you maximise expected reward minus times KL to the reference, the optimal policy is

that is, the reference policy reweighted by exponentiated reward. This is not usable directly because the partition function sums over all possible responses. But rearrange it to solve for the reward instead:

Now substitute this into the Bradley-Terry preference likelihood . Because the two responses share the same prompt , the intractable term appears in both and cancels exactly. What is left is a loss over the policy alone:

That is the formula in the box above, now with a reason.

Good and bad.

  • DPO — good: one model in memory, a stable supervised loss, no reward-model training and no sampling loop; reference log-probs can be precomputed once for the whole dataset.
  • DPO — bad: trains on a fixed, off-policy dataset, so it never sees the responses the current policy actually generates. A known pathology follows from the loss: nothing constrains the absolute likelihood of the chosen response, only the gap, so the optimiser can and often does reduce the probability of both responses while widening the difference — pushing mass onto entirely unseen text. Practical fixes include adding an SFT term on the chosen responses (this is roughly what RPO/CPO do), and running iterative DPO where you periodically sample from the current policy, label those samples, and retrain.
  • RLHF — good: the online loop keeps collecting fresh samples and scoring them, which is how it discovers and corrects new failure modes. Bad: four models, a hackable proxy, and a fragile optimisation.

Follow-up: What does control in DPO? It is the same KL strength as in RLHF, but it enters as the inverse temperature of the implicit reward. Small (say 0.01) means the policy is allowed to move far from the reference and fits preferences aggressively; large (0.5) keeps it tethered. Typical values are 0.1 to 0.5. Because the reference model appears explicitly in the loss, its log-probabilities can be precomputed once for the whole dataset, so DPO needs only one model in memory during training rather than PPO’s three or four.

Why the interviewer asks this. DPO is the standard alignment method most teams actually run; knowing that it comes from analytically solving the RLHF objective — rather than being a separately invented heuristic — is what distinguishes reading the paper from reading a blog summary.

Saying it out loud. “DPO comes from noticing that the KL-constrained RLHF objective has a closed-form optimal policy, and if you invert it you can write the reward in terms of the policy’s log-ratio against the reference. Plug that into the preference likelihood and the intractable normalising term cancels, because both responses share a prompt. So you end up with a plain supervised loss on preference pairs — no reward model, no sampling, no RL loop. The cost is that it’s off-policy: it only ever sees the pairs in your dataset, so it can’t discover new failure modes the way an online loop does. And there’s a known quirk where it lowers the probability of the chosen response too, as long as the gap widens.”


Q13: Explain PPO (Proximal Policy Optimization) in detail. Why is it used in RLHF?

In 30 seconds. “PPO exists so you can reuse a batch of samples for more than one gradient step. You do that with an importance ratio — new policy probability over old — but that ratio has ugly variance if the policy moves too far, so PPO clips it to a narrow band around one and takes the pessimistic branch with a min. Once you’ve pushed a good action up by twenty percent the gradient goes flat for that batch, but a bad action whose probability rose still gets the full correction.”

The short version.

  • What it is: a policy-gradient algorithm that prevents large policy updates by clipping the objective.
  • The ratio is what lets you reuse a batch; the clip is what stops the variance exploding.
  • Why clipping: prevents destabilising updates, changes the policy gradually, and makes the same data reusable across epochs.

Why PPO in RLHF:

  1. Stability: Language models are sensitive - need stable updates
  2. Sample efficiency: Human feedback is expensive - reuse data
  3. KL constraint: Keeps policy close to reference
  4. Proven: Works well in practice (ChatGPT, Claude)

PPO Algorithm:

  1. Collect trajectories with current policy
  2. Compute advantages A(s,a)
  3. For K epochs:
    • Compute ratio r(θ) = π_θ / π_θ_old
    • Compute clipped objective
    • Update policy
  4. Update old policy

Why it works.

Vanilla policy gradient computes from samples drawn by the current policy, so as soon as you take one gradient step the data is stale and must be thrown away. That is intolerable when each sample requires generating a full response from a large language model. Importance sampling fixes it: you can estimate an expectation under using samples from by reweighting each sample by . That is where the ratio in the objective comes from — it is what licenses multiple epochs over the same batch.

The problem is that importance sampling has unbounded variance: if the policy moves far from the sampler, some ratios blow up and a single sample dominates the gradient. TRPO handled this with a hard KL trust-region constraint and a second-order solve. PPO’s contribution is to get almost the same effect with a first-order trick — clip the ratio and take the pessimistic branch.

The advantage, and why RLHF needs a value model. asks “was this action better than the average action from this state”, and subtracting the state-value baseline removes variance without introducing bias. PPO estimates it with GAE (generalised advantage estimation), an exponentially weighted average over -step temporal-difference errors controlled by , which trades bias against variance. In RLHF the reward arrives only at the end of the response, so the value model’s job is to spread that single scalar back across hundreds of token-level decisions. This is precisely the component GRPO removes.

The math, and what it buys you.

L^CLIP(θ) = E[min(r(θ)A, clip(r(θ), 1-ε, 1+ε)A)]

Where:
- r(θ) = π_θ(a|s) / π_θ_old(a|s) (importance sampling ratio)
- A: Advantage estimate
- ε: Clipping parameter (typically 0.1-0.3)

Read the four cases and it becomes obvious what the formula buys you. With : if the advantage (the action was better than expected) and has already grown past , the clipped branch is flat, so the gradient is zero — you have already increased this action’s probability enough for one batch, stop. If and , no clipping applies and you get the full gradient. If and has fallen below , again flat, stop pushing it down. If and , the unclipped term is more negative, and the min selects it — so a bad action whose probability accidentally increased still gets a full corrective gradient. The min is what makes the bound pessimistic rather than merely bounded: it only ever removes incentive to move further, never removes a correction.

Good and bad.

  • Good: sample-efficient (multiple epochs per batch), first-order and cheap compared with TRPO’s second-order solve, and empirically the most reliable RLHF optimiser at scale.
  • Bad — memory. The full RLHF-PPO loop keeps four models live: the policy being trained, a frozen reference for the KL term, the reward model, and the value model. That is the practical reason PPO-based RLHF is hard to run, and the reason both DPO (drop the RL loop) and GRPO (drop the critic) found adoption.
  • Bad: many interacting hyperparameters (, epochs per batch, GAE , KL , entropy bonus), and a value model that is itself hard to calibrate on long generations.

Follow-up: What typically goes wrong when PPO training destabilises? Watch three numbers. If the KL to the reference climbs steadily, the policy is drifting and reward hacking usually follows — most implementations use an adaptive that increases when KL exceeds a target. If the clip fraction (the share of tokens hitting the clip boundary) rises above roughly 20-30%, the policy is moving too fast per batch, so lower the learning rate or take fewer epochs per batch. If entropy collapses, the policy has converged to a narrow set of phrasings — mode collapse — and an entropy bonus or a stronger KL term is the usual response.

Why the interviewer asks this. They want to see whether you can explain the clip as a variance-control mechanism for importance sampling, rather than as “it stops big updates”.

Saying it out loud. “PPO exists so you can reuse a batch of samples for more than one gradient step. You do that with an importance ratio — new policy probability over old — but that ratio has nasty variance if the policy moves too far, so PPO clips it to a narrow band around one and takes the pessimistic branch with a min. The effect is that once you’ve pushed a good action’s probability up by twenty percent, the gradient goes flat for that batch, but if a bad action’s probability went up you still get the full correction. In RLHF you also need a value model, because the reward only shows up at the end of the response and something has to spread that credit across all the tokens.”


Q14: What is GRPO (Group Relative Policy Optimization)? When is it useful?

In 30 seconds. “GRPO is PPO with the critic deleted. Instead of learning a value function to predict expected reward, you sample a whole group of completions for the same prompt — say sixteen — and use the group’s own mean reward as the baseline. So the advantage is just ‘did this attempt beat my other attempts at this problem’. That kills one of the four models in memory, and it pairs beautifully with verifiable rewards. The cost is generation: sixteen completions per prompt instead of one.”

The short version.

PPOGRPO
Baseline for the advantageLearned value networkMean reward of samples from the same prompt
Models in memoryPolicy, reference, reward, valuePolicy, reference (+ reward, unless rule-based)
Extra costTraining and calibrating a criticGenerating completions per prompt
Best fitGeneral RLHF with a learned reward modelVerifiable rewards: maths, code, unit tests
  • GRPO (Shao et al., DeepSeekMath, 2024; the algorithm behind DeepSeek-R1) is a variant of PPO that removes the learned value network.
  • The “group” is a group of sampled responses to the same prompt, not a group of users. For each prompt, the policy samples completions (typically 8-64), each is scored, and the group’s own reward statistics serve as the baseline that PPO would otherwise get from a critic.
  • Use cases: reasoning with verifiable answers — mathematics, competitive programming, unit-test-checked code — its original and strongest domain; and any setting where sampling several completions is cheap relative to training a critic.

Note on a correction. An earlier version of this answer described GRPO as optimising across demographic or user groups with different preferences. That is not what GRPO is. The “group” is a group of sampled completions for one prompt, and the method’s purpose is to eliminate the value network. The text above has been corrected.

Why it works.

Any policy-gradient method needs a baseline subtracted from the reward, because for any that does not depend on the action — so subtracting a baseline reduces variance without introducing bias. PPO learns that baseline with a value network. GRPO observes that if you sample completions from the same prompt, their mean reward is already an unbiased, zero-cost estimate of that prompt’s expected return. No network, no training, no calibration drift. Dividing by the group standard deviation additionally normalises the advantage scale across prompts, so an easy prompt where everything scores 0.9-1.0 and a hard prompt where everything scores 0.0-0.1 contribute comparably sized gradients.

Why GRPO, in three points.

  • No critic: PPO needs a value model roughly the size of the policy, so it holds four models in memory (policy, reference, reward, value). Dropping the critic cuts memory and removes a second network that itself has to be trained and can be badly calibrated on long generations.
  • Natural fit for verifiable rewards: When the reward is a rule-based checker — does the maths answer match, do the unit tests pass — you get a clean scalar per sample and no reward model is needed either, leaving just policy and reference.
  • Comparison is the signal: The advantage says “was this attempt better or worse than my other attempts at the same problem”, which is exactly the credit-assignment question, and it needs no learned estimate of expected return.

How this pairs with verifiable rewards. GRPO became prominent because it composes so cleanly with rule-based rewards. If your reward is “does the final answer match” or “do the tests pass”, you have removed the reward model; GRPO removes the value model; and you are left with a policy and a frozen reference. That configuration — sample many attempts, check them, upweight the ones that worked — is what produced the long chain-of-thought behaviour in DeepSeek-R1, where the model learned to backtrack and re-derive without ever being shown a demonstration of doing so.

The math, and what it buys you.

For a prompt , sample outputs with rewards . The advantage for output is the reward standardised within the group — this one line is what replaces the entire value network:

and this scalar is assigned to every token of that output. The objective is then the usual PPO clipped surrogate with a KL term:

where is the per-token importance ratio.

A worked example.

  • Prompt: a maths problem. Sample 8 chains of thought; 3 reach the right answer, 5 do not.
  • Rewards are , so mean and std .
  • Advantages are for the three correct chains and for the five wrong ones, pushing probability mass toward whatever the successful chains did — with no value network anywhere in the loop.

Good and bad.

  • Good: one fewer model to hold and train; a baseline that cannot be miscalibrated because it is measured, not predicted; and a clean composition with rule-based rewards.
  • Bad — the standard-deviation divisor. If every completion in a group gets the same reward — all correct or all wrong — the standard deviation is zero, the advantage is undefined or zero, and the prompt contributes nothing; those prompts are simply wasted compute. And dividing by the standard deviation systematically upweights prompts where the group happened to disagree, which is a difficulty bias rather than a learning signal. Later variants (Dr. GRPO and the loss-normalisation fixes in DAPO) drop the standard-deviation divisor and change the token-length normalisation for exactly these reasons.
  • Bad — sampling cost. Group size is a real hyperparameter: too small and the baseline is noisy, too large and you are paying for redundant samples.

Follow-up: What is the practical cost? Compute at sampling time. You are generating full completions per prompt instead of one, so a group size of 16 means 16 times the generation work per prompt — and generation, not the gradient step, dominates RL wall-clock for LLMs. The trade you are making is critic memory and critic instability in exchange for sampling throughput, which is a good trade when generation is well-optimised and a bad one when it is not. Group size is a real hyperparameter: too small and the baseline is noisy, too large and you are paying for redundant samples.

Why the interviewer asks this. It is a recency check with substance: GRPO is only useful to know if you can say what it removes and what it costs, not just that DeepSeek used it.

Saying it out loud. “GRPO is PPO with the critic deleted. Instead of learning a value function to tell you what reward to expect from a prompt, you sample a whole group of completions for that prompt — say sixteen — and use the group’s own mean reward as the baseline, standardised by the group’s standard deviation. So the advantage is just ‘did this attempt beat my other attempts at the same problem’. That kills one of the four models you’d otherwise hold in memory, and it pairs beautifully with verifiable rewards like ‘did the tests pass’, because then you don’t need a reward model either. The cost is generation: you’re producing sixteen completions per prompt instead of one.”


Q15: What are the main challenges in RL alignment? How do you address them?

In 30 seconds. “Almost all of these are one problem wearing different hats: you’re optimising a learned proxy for human judgement, and the policy is actively hunting for places where that proxy is wrong. True quality goes up, peaks, then falls while the reward score keeps climbing. The main lever is the KL penalty, because it bounds how far you can get from the region where the reward model was actually trained.”

The short version.

ChallengeProblemSolution
Reward hackingModel maximises reward in ways that miss the intentCareful reward design, KL penalty, monitoring
Distribution shiftPolicy moves; reward model was trained on the old distributionRetrain the RM periodically, regularisation
Mode collapsePolicy collapses to a single response patternKL penalty, entropy bonus, diverse training data
InstabilityTraining divergesPPO clipping, gradient clipping, LR scheduling
Feedback qualityInconsistent or biased human labelsMultiple annotators, quality control, bias detection

Why it works.

The common root. Four of the five challenges above are the same phenomenon seen from different angles: you are optimising a proxy (a learned reward model) rather than the thing you care about (human judgement), using a policy that actively searches the proxy’s input space. Reward hacking is the proxy being exploited; distribution shift is the proxy being evaluated off its training distribution; mode collapse is the policy concentrating on the narrow region where the proxy scores highest; instability is the optimisation running away in that direction. Framing it as one problem rather than five is what makes the mitigations cohere — the KL penalty appears in three of the five rows because it directly bounds how far into unmeasured territory the policy is allowed to go.

On feedback quality specifically. The failure is not only noise but systematic bias, and the biases are known: annotators reliably prefer longer responses, more confident phrasing, and more formatting (lists, bold headers) independent of content. Length bias is severe enough that some RLHF runs produce models that are longer and no better, and length-controlled evaluation exists precisely because of it. Mitigations are measurement-first — check the correlation between reward and response length, and if it is high, either debias the reward (subtract a length term) or resample the preference data to decorrelate them.

The math, and what it buys you.

A number worth having. Gao, Schulman and Hilton’s scaling-law study of reward-model overoptimisation found that true reward, plotted against the square root of the KL divergence from the initial policy, rises and then falls in a predictable arc, with the peak moving further out as the reward model gets larger and is trained on more data. The practical implication is concrete: KL distance is the right x-axis for deciding when to stop, and “stop when the RM score plateaus” is the wrong criterion because the RM score keeps rising past the point where quality starts dropping.

Follow-up: How would you tell reward hacking apart from genuine improvement? Use a held-out signal the policy is not being optimised against. Concretely: a separate reward model trained on different data or with a different seed (if scores diverge between the two, at least one is being gamed), pairwise human evaluation on a fresh sample, and task benchmarks the RM never saw. Add cheap behavioural monitors for the known degenerate modes — mean response length, refusal rate, n-gram repetition, entropy — because hacking usually shows up as a sharp change in one of those before it shows up in an aggregate score.

Why the interviewer asks this. The list is easy to memorise; they are listening for whether you understand that these are symptoms of proxy optimisation and can name what you would actually monitor.

Saying it out loud. “Almost all of these are one problem wearing different hats: you’re optimising a learned proxy for human judgement, and the policy is actively hunting for places where that proxy is wrong. So true quality goes up, peaks, and then falls while the reward score keeps climbing. The main lever is the KL penalty back to the reference model, because it bounds how far you can get from the region where the reward model was actually trained. Beyond that I’d hold out a second reward model and some real human eval as an unhacked signal, and I’d watch for the obvious tells — response length creeping up, entropy collapsing, refusal rate spiking.”


Q16: How do you prevent reward hacking in RLHF?

In 30 seconds. “Reward hacking isn’t a bug you fix — it’s what optimisation does to any imperfect proxy; the policy’s whole job is to find where your reward model is wrong. The concrete versions are consistent: longer, more sycophantic, more heavily formatted, more confident hedging. The main defence is the KL penalty, then a pessimistic reward-model ensemble, then monitoring length, entropy and refusal rate — because hacking shows up there before it shows up in any aggregate score.”

The short version.

  • What it is: the model finds unintended ways to maximise reward (e.g. always saying “I can’t answer”).
  • Prevention: careful reward design (multiple signals, penalise known hacks); regularisation (KL penalty); reward-model robustness (diverse training, bias detection); monitoring (track patterns, detect anomalies); constrained optimisation (hard/soft constraints); iterative refinement (identify hacks, refine the reward).
  • Expect it by default. You need a detection story, not only a prevention story.
Hack that actually shows upWhat the RM learned instead of quality
Length inflationAnnotators mildly prefer longer answers, so the policy pads
SycophancyAgreeing with the user’s stated view scores well
Formatting theatreBullets, bold headers and a confident summary score well regardless of content
Hedged non-answersOn hard prompts a fluent refusal outscores a wrong attempt
Confident fabricationCertainty is rewarded, uncertainty is not, so calibration degrades

Why it works.

Why this is hard rather than just fiddly. Reward hacking is not a bug in a particular reward model; it is what optimisation does to any imperfect proxy. Goodhart’s law in its sharpest form: the reward model and human judgement agree on the distribution where preferences were collected, and the optimiser’s job is to leave that distribution. So the goal is never “build an unhackable reward” — it is to make the gap costly to reach and to notice when it has been reached.

Concrete hacks that actually show up. Naming these is more convincing in an interview than abstractions. Length inflation: annotators mildly prefer longer answers, so the policy learns to pad, and reward rises with word count. Sycophancy: agreeing with the user’s stated view scores well, so the model stops contradicting false premises. Formatting theatre: bullet points, bold headers and a confident summary paragraph score well independent of content. Hedged non-answers: on hard prompts, a fluent refusal outscores a wrong attempt, so the model learns to refuse more. Confident fabrication: certainty is rewarded, uncertainty is not, so calibration degrades. Each is a case of the reward model having learned a correlate of quality rather than quality.

The one mechanism to explain properly. Of the six prevention items listed above, the KL penalty is the load-bearing one, and the reason is stated precisely in Q17: it caps how far the policy can travel from the region where the reward model’s judgements were actually validated. Reward-model ensembles are the second most useful, because a hack usually exploits an idiosyncrasy of one model’s error surface; taking the minimum (pessimistic) score across an ensemble, rather than the mean, penalises responses that any member distrusts and measurably delays overoptimisation.

Follow-up: What if the reward is a verifier rather than a learned model? It is harder to hack but not immune, and the failures are different in kind: unit tests get special-cased (if input == test_case_1: return 42), maths answers get asserted without a valid derivation, and any reward that only checks the final answer will accept a correct answer reached by an incoherent chain. So the mitigation shifts from KL-style constraints to test coverage, hidden held-out tests, and process-level checking of the reasoning rather than only the outcome.

Why the interviewer asks this. Anyone can list mitigations; they want to hear that you expect hacking by default and have a detection story, not just a prevention story.

Saying it out loud. “I’d start by saying reward hacking isn’t a bug you fix, it’s what optimisation does to any imperfect proxy — the policy’s whole job is to find where your reward model is wrong. The concrete versions are pretty consistent: answers get longer, more sycophantic, more heavily formatted, and more prone to a confident hedge instead of a real attempt. The main defence is the KL penalty, which keeps the policy near the distribution the reward model was actually trained on. After that I’d ensemble a couple of reward models and take the pessimistic score, and I’d monitor length, entropy and refusal rate, because hacking normally shows up there before it shows up in any aggregate metric.”


Q17: Explain the KL penalty in RLHF. Why is it important?

In 30 seconds. “The KL penalty measures how far the policy has drifted from the frozen reference model and charges it for that drift. It matters because the reward model was only ever validated near the reference distribution — that’s where the preference data came from — so KL distance is a direct measure of how far outside your measurements you’ve wandered. Too small a beta and the policy runs off and hacks the reward; too large and it can’t learn.”

The short version.

  • What it is: KL divergence measures how different the policy is from the reference; the penalty prevents large deviations.
  • Why it matters: prevents mode collapse (keeps the policy diverse), prevents reward hacking (constrains to reasonable behaviours), maintains SFT capabilities, adds stability, and acts as a trust region.
  • Choosing : too small and the policy deviates too much; too large and it cannot learn. Typical — but it is usually adaptive, not fixed.
KL(π_θ || π_ref) = E[log(π_θ(a|s) / π_ref(a|s))]

In practice:
KL_penalty = β * (log π_θ - log π_ref)

Why it works.

What the penalty is doing, geometrically. KL divergence measures how much the trained policy’s distribution has moved from the frozen reference. Adding to the reward turns unconstrained reward maximisation into a trust region: the policy may buy reward, but it pays in distance, and the exchange rate is . The reason this is the right currency is that the reward model’s judgements are only validated near the reference distribution — that is where the preference data was collected. KL distance is therefore a direct proxy for “how far outside my measurement is this”.

How it is actually computed. You cannot evaluate the true expectation over all sequences, so implementations use the per-token log-ratio of the sampled tokens, , as a single-sample estimator and subtract times it from the reward at each token. That naive estimator is unbiased but high-variance and can go negative, which is confusing to read on a dashboard; the common fix is the low-variance estimator where , which is always non-negative and much less noisy. Note also the implementation choice: adding the penalty into the reward (so it flows through the advantage and the value function) behaves differently from adding it as a separate loss term, and the two are not equivalent — the original RLHF papers put it in the reward.

Adaptive . Because the right depends on the reward model’s scale, most implementations do not fix it. They set a target KL and use a controller that raises when measured KL exceeds the target and lowers it when it falls below. This makes the hyperparameter you tune (“how far am I willing to drift”) interpretable in a way a raw coefficient is not.

The math, and what it buys you.

A small worked example of what buys. Suppose at some token the reference gives probability 0.10 to the token the policy sampled and the policy now gives it 0.60. The log-ratio is . With the penalty is reward units at that token; with it is . If the reward model’s scale is such that a typical good-versus-bad gap is around 1 unit, then at this single-token deviation has consumed most of the available reward and the policy will not make it unless the payoff is genuinely large — whereas at it is nearly free. That is the whole tuning intuition, and it also explains why cannot be transferred between runs without checking the reward model’s scale.

Good and bad.

  • Good: it is the single most effective brake on reward hacking, it preserves SFT capabilities, and — as an adaptive target-KL controller — it turns an opaque coefficient into a quantity you can reason about.
  • Bad: too large and learning stalls; the naive per-token estimator is noisy and can read negative; and the direction used is mode-seeking, which is part of why RLHF measurably reduces output diversity.

Follow-up: Which direction of KL is used, and does it matter? RLHF uses the forward-from-the-policy form , which is mode-seeking: it heavily punishes the policy for putting mass where the reference puts almost none, but does not punish it for abandoning modes the reference covered. That asymmetry is convenient — it blocks the policy from inventing wholly new behaviour — but it is also part of why RLHF reduces output diversity, since dropping modes is cheap under this direction. The reverse direction would be mass-covering and would preserve diversity, but it would require sampling from the reference, which is more expensive.

Why the interviewer asks this. It is the one hyperparameter that decides whether an RLHF run works, so knowing what it trades off — and that it is usually adaptive — is a strong signal of hands-on experience.

Saying it out loud. “The KL penalty measures how far the policy has drifted from the frozen reference model and charges the policy for that drift. The reason it matters is that your reward model was only ever validated near the reference distribution — the preference data came from there — so KL distance is basically a measure of how far outside your measurements you’ve wandered. Too small a beta and the policy runs off and hacks the reward; too large and it can’t learn anything. In practice people don’t fix beta at all — they set a target KL and let a controller adjust beta to hit it, which makes the knob you’re tuning something you can actually reason about.”


See 08_training_techniques/rl_alignment_qa.md for even more detailed answers!


Optimization

Q18: Explain the Adam optimizer.

In 30 seconds. “Adam is two ideas glued together. The first moment is momentum — an exponential average of gradients, so consistent directions build up and noisy ones cancel. The second moment averages squared gradients, and dividing by its square root gives every parameter its own effective learning rate, roughly the signal-to-noise ratio of that parameter’s gradient. The bias correction exists because both averages start at zero and are wildly too small at step one.”

The short version.

  • Adam = Adaptive Moment Estimation.
  • First moment (m): exponential moving average of gradients — momentum.
  • Second moment (v): exponential moving average of squared gradients — a per-parameter learning rate.
  • Bias correction: fixes the toward-zero bias of both averages in early steps.
  • Defaults: (momentum), (variance), (learning rate).
  • Cost: two extra values per parameter in memory.
m_t = β1 * m_{t-1} + (1-β1) * g_t
v_t = β2 * v_{t-1} + (1-β2) * g_t²
m_hat = m_t / (1 - β1^t)
v_hat = v_t / (1 - β2^t)
θ_t = θ_{t-1} - α * m_hat / (√v_hat + ε)

Why it works.

Adam is momentum and RMSProp stacked, plus a correction for how they start.

The first moment is momentum. is an exponential moving average of past gradients, which with has an effective window of about steps. Averaging helps because minibatch gradients are noisy estimates of the true gradient: components that point consistently in one direction survive the average, components that flip sign cancel. In a long narrow ravine — the typical shape of a neural loss surface — this damps the oscillation across the valley and accumulates speed along it.

The second moment is a per-parameter learning rate. tracks the average squared gradient with a much longer window, about 1000 steps at . Dividing the step by means a parameter whose gradients have been consistently large takes small steps and one whose gradients have been small takes large ones. The effective step size becomes roughly , which is close to a signal-to-noise ratio: parameters with a consistent gradient direction move at nearly the full learning rate, parameters whose gradient is mostly noise barely move. This is why Adam works out of the box on problems — like transformers, with embeddings whose gradients are extremely sparse and layer norms whose gradients are not — where a single global learning rate would be badly wrong for most parameters.

Epsilon sits inside the square root’s denominator to stop division by zero, but it also quietly sets a floor: with , any parameter whose gradient RMS falls below that gets a step proportional to rather than a normalised one. Raising to or is a standard stability fix for large-model training.

The math, and what it buys you.

Bias correction is not cosmetic — here is the number that shows it. Both averages start at zero, so early on they are biased toward zero. Concretely: at , , so — about 30 times too small, which would make the first step about 30 times too large and can blow the model up immediately. The correction divides by , which at is exactly , restoring . The correction decays to nothing as , so it only matters for roughly the first few thousand steps — precisely the fragile part of training. (Even with it, transformers usually still need learning-rate warmup, because the variance of Adam’s update, not just its mean, is large when is estimated from few samples.)

Worked micro-example — what the adaptivity actually does. Take a parameter with a steady gradient every step, . After a few steps and , so and the update is — the full learning rate. Now a parameter whose gradient alternates : but still, so the update is near zero. Same gradient magnitude, opposite treatment, based purely on consistency.

Good and bad.

  • Good: works out of the box across very different architectures; per-parameter adaptivity handles sparse embedding gradients and dense norm gradients in the same run; momentum smooths minibatch noise.
  • Bad: memory — two extra values per parameter; sensitivity to at scale; and it still needs learning-rate warmup for transformers despite the bias correction.

Follow-up: Why does Adam use so much memory? It stores and per parameter, so with FP32 master weights the optimiser state is 8 bytes per parameter on top of the 4 bytes of weights and 4 of gradients — roughly 16 bytes per parameter in total, meaning a 7B model needs about 112 GB just to hold training state before activations. This is exactly what ZeRO shards across devices, what 8-bit Adam quantises, and what memory-light optimisers like Adafactor (which factorises into row and column statistics) and Lion (which keeps only momentum) are trying to avoid.

Why the interviewer asks this. Everyone uses Adam; few can say what the second moment is doing or why the bias correction exists, and the bias-correction question in particular has a crisp right answer.

Saying it out loud. “Adam is two ideas together. The first moment is momentum — an exponential average of the gradients, so consistent directions build up and noisy ones cancel. The second moment is an average of squared gradients, and dividing by its square root gives every parameter its own effective learning rate; roughly you’re stepping by the signal-to-noise ratio of that parameter’s gradient. The bias correction is there because both averages start at zero, so early on they’re way too small — at step one the second moment is a thousandth of its true value, which would make your first step enormous. And the cost is memory: two extra values per parameter.”


Q19: What’s the difference between Adam and AdamW?

In 30 seconds. “For plain SGD, adding an L2 term to the loss and decaying the weights are the same operation. For Adam they aren’t, because the L2 term goes through the second-moment normalisation along with the real gradient — so parameters with big gradient histories get their decay divided down and end up barely regularised, while quiet parameters get hammered. AdamW applies the decay straight to the weights after the adaptive step, so everything shrinks by the same proportion.”

The short version.

Adam (coupled L2)AdamW (decoupled decay)
Where the penalty entersAdded to the gradientAdded to the weight update, after the adaptive step
Is it normalised by ?Yes — decay strength depends on gradient historyNo — uniform relative shrinkage
EffectBusiest parameters are least regularised (backwards)Every parameter shrinks by the same proportion
LR / decay interactionCoupled — tuning LR silently changes regularisationDecoupled — hyperparameters transfer between runs
# Adam: weight decay in gradient
gradient = gradient + weight_decay * params

# AdamW: weight decay separate
params = params - lr * (adam_update + weight_decay * params)

Why it works.

L2 regularisation and weight decay are the same thing for plain SGD and different things for Adam — that is the whole content of the AdamW paper.

With L2 regularisation you add to the loss, so the gradient becomes . Feed that into Adam and the penalty term goes through the same second-moment normalisation as everything else. Because the denominator is large for parameters with large historical gradients, the decay those parameters receive is divided down. The consequence is backwards: parameters with big, consistent gradients — the ones doing the most work and most at risk of growing large — get the least regularisation, while parameters with tiny gradients get the most. The decay strength ends up coupled to the gradient history, which is not what anyone intends when they set a weight decay value.

AdamW removes the penalty from the gradient entirely and applies it directly to the weights after the adaptive step. Now every parameter shrinks by the same relative amount per step regardless of its gradient statistics, which is what “weight decay” is supposed to mean. A practical side effect: the two hyperparameters decouple, so tuning the learning rate no longer silently changes the effective regularisation, which is why AdamW’s hyperparameters transfer between runs far better.

The math, and what it buys you.

Adam with L2 in the gradient gives

— note the sitting inside the normalisation. AdamW instead applies

Numeric illustration of what that difference costs you. Two parameters, both at , both with . Parameter A has had a gradient RMS around ; parameter B around . Under Adam-with-L2 the decay contributions are roughly , so A gets and B gets — a hundredfold difference in regularisation strength that nobody asked for. Under AdamW both get exactly .

Good and bad.

  • AdamW — good: better generalisation, more principled decay, and hyperparameters that transfer. Bad: because the decay now hits every parameter uniformly, you generally want to exclude biases, LayerNorm gains and (often) embeddings from it — decaying a normalisation gain toward zero has no regularising interpretation and just fights the layer. Every serious training script has a parameter-group split doing exactly this.
  • Practical values: for large transformer pretraining, for fine-tuning; note these are much larger than classic L2 values, another consequence of the rescaling.

Follow-up: If AdamW is strictly better, why does torch.optim.Adam still have a weight_decay argument? Because it implements the coupled L2 form, which is what the original Adam paper described and what a lot of older code depends on for reproducibility. Adam(weight_decay=0.01) and AdamW(weight_decay=0.01) do genuinely different things — a real source of silent discrepancies when porting a config between codebases.

Why the interviewer asks this. It looks like trivia but has a real mechanism behind it, and the answer reveals whether you have ever debugged why a regularisation setting did not behave as expected.

Saying it out loud. “For plain SGD, adding an L2 term to the loss and decaying the weights directly are the same operation. For Adam they aren’t, because the L2 term goes through the second-moment normalisation along with the real gradient. That means parameters with big gradient histories get their decay divided down and end up barely regularised, while quiet parameters get hammered — the opposite of what you want. AdamW just applies the decay straight to the weights after the adaptive step, so everything shrinks by the same proportion. It also decouples the two hyperparameters, so changing your learning rate stops secretly changing your regularisation.”


Regularization

Q20: Explain L1 vs L2 regularization.

In 30 seconds. “Both penalise the size of the weights, but the shape of the penalty changes the outcome. L2’s gradient is proportional to the weight, so as a weight gets small the pressure on it gets small too — it shrinks toward zero and never arrives. L1’s gradient is a constant lambda times the sign, so the push is just as strong at 0.001 as at 10; if the data’s pull is weaker than lambda, the weight is pinned at exactly zero. That’s why L1 selects features and L2 doesn’t.”

The short version.

L1 (Lasso)L2 (Ridge)
Penalty
GradientConstant: Linear:
Effect on weightsMany become exactly 0Shrink toward 0, never reach it
GeometryDiamond with corners on the axesSphere, no corners
Use forFeature selection, sparsity, interpretabilityGeneralisation — the standard choice
Correlated featuresPicks one arbitrarily, zeroes the other; unstableSplits weight between them

Elastic Net: Combines both.

Why it works.

This is the part an interviewer will push on, and the cleanest explanation is about what happens near zero. Consider a single weight with data-fit gradient at the optimum of the unregularised loss.

Under L2, the penalty gradient is . As that gradient also , so the shrinkage force vanishes exactly where you would need it to be strongest. The weight settles wherever the data-fit gradient balances , which for any nonzero is a nonzero .

Under L1, the penalty gradient is , which has magnitude no matter how small is. So there is a constant force pushing toward zero that does not weaken on approach. If the data-fit gradient at has magnitude less than , it cannot overcome that force and the weight is pinned at exactly zero.

The geometric picture, stated properly. The constraint region for L1 is a diamond (a cross-polytope) whose corners lie on the axes; for L2 it is a sphere with no corners. The solution is where the elliptical contours of the squared-error loss first touch the constraint region, and an ellipse touching a diamond will generically touch at a corner — a corner being exactly a point where some coordinates are zero. A sphere has no preferred points, so contact happens at a generic location with all coordinates nonzero. This picture and the soft-threshold formula below are two views of the same fact.

The math, and what it buys you.

Solve the one-dimensional case under L2 and you get proportional shrinkage: — everything scaled down by a constant factor, nothing set to zero. The closed form for L1 in the orthonormal case is the soft-threshold operator, and it states the sparsity result outright:

which says it plainly: subtract from the magnitude, and if that goes negative, clamp to zero.

A worked number. With and OLS estimates : L1 gives — the two small coefficients are eliminated and the large one is shifted. L2 with the same gives — everything shrunk by a third, nothing removed. Same regularisation strength, completely different structure in the answer.

Good and bad.

  • L1 — good: genuine feature selection and interpretable sparse models. Bad: not differentiable at zero, so plain gradient descent cannot land exactly on zero — solvers use coordinate descent, proximal gradient (ISTA/FISTA), or LARS; and it handles correlated features badly, picking one of two near-identical predictors essentially arbitrarily, unstably across resamples.
  • L2 — good: smooth, closed-form, stable under correlated features, the default. Bad: never produces exact zeros, so it does no selection.
  • Elastic net, , exists specifically to keep sparsity while making correlated groups enter or leave together.

Follow-up: Should you regularise the intercept? No. The intercept is not a feature weight; shrinking it biases predictions toward zero output rather than toward a simpler function, and it makes the fit depend on where you happened to centre the target. Standard implementations exclude it — and for the same reason you should standardise features before regularising, since otherwise the penalty is applied in units that depend on arbitrary measurement scales.

Why the interviewer asks this. The “L1 gives sparsity” fact is universal knowledge; the constant-gradient-near-zero explanation is not, and it is a clean test of whether you think in terms of what the optimiser actually does.

Saying it out loud. “Both add a penalty on the size of the weights, but the shape of the penalty changes the outcome. L2’s gradient is proportional to the weight, so as a weight gets small the pressure on it gets small too — it shrinks toward zero and never arrives. L1’s gradient is a constant lambda times the sign, so the push toward zero is just as strong at 0.001 as at 10. If the data’s pull on that weight is weaker than lambda, it gets pinned at exactly zero. That’s why L1 does feature selection and L2 doesn’t. Geometrically it’s the diamond-versus-circle picture — the diamond has corners on the axes and the loss contours tend to hit a corner.”


Q21: How does dropout work?

In 30 seconds. “During training you randomly zero activations with probability p and scale the survivors up by 1/(1-p), so the expected value into the next layer is unchanged; at inference you do nothing at all. It helps because no unit can depend on a specific other unit being present — you don’t get fragile co-adapted feature groups. The old ‘use 0.5’ advice is out of date: transformers use about 0.1 and large-scale pretraining often uses none.”

The short version.

  • Training: zero each activation with probability ; scale survivors by . Prevents co-adaptation.
  • Inference (inverted dropout, what frameworks implement): a no-op.
  • Classic formulation: no scaling at training, multiply all activations by at test time. Equivalent in expectation.
  • Why it works: prevents co-adaptation; forces robustness; acts as an ensemble of subnetworks.
  • Rates (classic advice): input 0.1-0.2, hidden 0.5, output usually none. Modern practice: ~0.1 in transformers, often 0 in LLM pretraining.

Why it works.

Why it regularises, in three compatible framings. Co-adaptation: a unit cannot rely on a specific other unit being present, because that other unit vanishes half the time, so features must be individually useful rather than useful only in a fixed committee. Implicit ensembling: a network with droppable units defines subnetworks sharing weights; training samples one per minibatch and inference with the scaled weights approximates averaging over all of them, which is a very cheap ensemble. Noise injection: dropout is multiplicative Bernoulli noise on activations, and it can be shown that for a linear model with squared loss it reduces exactly to an L2 penalty scaled by each feature’s magnitude — an adaptive weight decay.

The two implementations, and which one you are using. Classic dropout zeroes activations with probability at training time and does nothing else, then at test time multiplies every activation by to match the expected value. Inverted dropout — what PyTorch, TensorFlow and everyone else actually implements — does the scaling at training time instead: zero with probability , then divide the survivors by . Test time is then a pure no-op, which is what you want, because inference should not need to know the training configuration. (The bullet list above states the classic form as well; note that in real frameworks the scaling happens during training and inference does nothing.)

The math, and what it buys you.

One line explains the mysterious : if a unit has value and survives with probability , then

The expectation is preserved exactly, so the layers downstream see inputs with unchanged mean magnitude and do not have to be retuned between training and inference. That is the entire purpose of the factor.

Good and bad.

  • Good: an extremely cheap ensemble; effective on small datasets and fully connected layers; no extra parameters.
  • Bad — where the “0.5 for hidden layers” advice no longer holds. That rate comes from 2012-era fully connected vision networks. Convolutional layers use little or no dropout (spatial correlation means zeroing individual pixels removes little information — use DropBlock or SpatialDropout if you want it), and transformers use around 0.1 on attention weights and on the residual branches. Large-scale language model pretraining frequently uses dropout of 0 entirely, because with a corpus far larger than the parameter count the model is not in the overfitting regime and dropout just slows convergence; it comes back for fine-tuning on small datasets.
  • Bad — a real interaction to know. Dropout and batch normalisation conflict. Dropout changes the variance of activations between training and inference, while batch norm has stored running statistics estimated under the training-time (noisy) distribution, so the two disagree at test time and accuracy drops. The usual resolutions are to put dropout only after all batch-norm layers, or to drop one of the two — which is part of why modern architectures with layer norm and heavy data augmentation often use very little dropout.

Follow-up: What is Monte Carlo dropout? Leave dropout on at inference and run the same input through several times, then look at the spread of the predictions. Gal and Ghahramani showed this approximates Bayesian inference in a deep Gaussian process, so the variance across runs is a usable uncertainty estimate. It is one of the cheapest ways to get calibrated uncertainty out of a network that was not designed for it — at the cost of several forward passes per prediction.

Why the interviewer asks this. The scaling factor is the detail that separates people who have read the paper from people who have used the API, and the “why 1/(1-p)” question has an exact answer.

Saying it out loud. “During training you randomly zero out activations with probability p, and you scale the survivors up by one over one minus p so the expected value going into the next layer doesn’t change. Then at inference you do nothing at all — that’s inverted dropout, which is what every framework actually implements. The reason it helps is that no unit can depend on any specific other unit being there, so you don’t get fragile co-adapted groups of features; another way to say it is that you’re training an exponential ensemble of subnetworks that share weights. The old advice of 0.5 is out of date though — transformers use about 0.1, and large-scale pretraining often uses none at all.”


Bias & Variance

Q22: Explain bias-variance tradeoff.

In 30 seconds. “Squared test error splits into bias squared, variance, and irreducible noise. Bias is how far the average model over all possible training sets is from the truth — the model class being too rigid. Variance is how much your particular fit moves when you resample the data. I care because it’s a diagnosis: high training error means bias and more data won’t help; low training error with a big test gap means variance, and data or regularisation will.”

The short version.

High bias (underfitting)High variance (overfitting)
CauseModel too simpleModel too complex / too sensitive to the sample
SymptomHigh train error, high test error (similar)Low train error, high test error (big gap)
FixMore capacity, better features, train longerMore data, regularisation, simpler model
TradeSimple model: high bias, low varianceComplex model: low bias, high variance
  • Irreducible error is label noise; no model removes it, which is why 100% accuracy is usually not the target.
  • Goal: find the balance where the sum of the two controllable terms is minimised.

Why it works.

Read the decomposition’s terms carefully, because the definitions matter. Bias is how far the average model — averaged over training sets you might have drawn — is from the truth; it is a property of the model class, not of any one fit. Variance is how much a particular fit bounces around that average when the training set changes. Irreducible error is label noise. The reason there is a tradeoff at all is that the two controllable terms respond in opposite directions to model flexibility: more capacity lets the average fit track the truth (bias down) but also lets each individual fit chase the noise in its particular sample (variance up).

Making it concrete. Fit polynomials to points from a sine curve plus noise. Degree 1: every training set gives roughly the same straight line, so variance is tiny, but no line is close to a sine, so bias is large. Degree 15: each training set gives a wildly different wiggly curve that passes through its own points exactly, so bias is near zero and variance is enormous. Degree 3-4 is where the sum is minimised.

How the standard fixes map onto the terms. More training data reduces variance and does nothing to bias — which is why adding data does not fix an underfitting model. Regularisation adds bias deliberately to buy a larger reduction in variance. Bagging (random forests) averages many high-variance, low-bias trees, and because averaging roughly-independent estimators divides variance by up to while leaving bias unchanged, it attacks variance specifically; feature subsampling exists to decorrelate the trees so that division is closer to real. Boosting works the other way: it fits shallow, high-bias stumps sequentially, each correcting the previous residuals, so it reduces bias — which is why boosted models can overfit with too many rounds while random forests largely do not.

The math, and what it buys you.

The whole discussion rests on one exact identity. For squared error, the expected test error at a point — expectation taken over random draws of the training set — decomposes into three terms:

What it buys you is the diagnosis: because the terms are separate and additive, each of your training numbers points at exactly one of them.

Good and bad.

  • The framework is good for: deciding what to do next from two error numbers, and for explaining why bagging and boosting are different tools.
  • One honest caveat worth raising. The classical U-shaped curve is not the whole story for modern overparameterised networks. Past the interpolation threshold — where the model has enough capacity to fit the training data exactly — test error can decrease again with further capacity, a phenomenon called double descent, observed in both model size and training time. So “more parameters means more variance” is a reliable rule for classical models and an unreliable one for large neural networks, where implicit regularisation from the optimiser changes the picture.

Follow-up: You have 8% training error and 10% test error, and human performance is 1%. What do you do? The gap is only 2 points, so variance is not the problem; the 7-point gap between human performance and training error is bias. Adding data or regularising will not help. Increase capacity, train longer, engineer better features, or check whether the input actually contains the information needed. The diagnostic move is always to compare training error against an irreducible-error estimate first, and only then look at the train-test gap.

Why the interviewer asks this. It is really a debugging question in disguise — they want to know whether, shown two error numbers, you would reach for the right fix.

Saying it out loud. “If you decompose squared test error, it splits into bias squared, variance, and irreducible noise. Bias is how far the average model over all possible training sets is from the truth — that’s about the model class being too rigid. Variance is how much your particular fit moves around when you resample the data. Simple models are stable but wrong; complex ones fit their own sample beautifully and don’t generalise. The reason I care is diagnosis: if training error is already high, that’s bias, and more data won’t help — I need capacity. If training error is low and test error is much higher, that’s variance, and data or regularisation will help.”


Information Theory

Q23: Explain entropy. What does it measure?

In 30 seconds. “Entropy is the expected surprise of a distribution — surprise being minus log p, which is the only form that makes independent events add. The concrete meaning is compression: entropy in bits is the smallest average number of bits per symbol you can encode the source in. A fair coin is one bit and you can’t do better; a ninety-ten coin is under half a bit because you can exploit the skew.”

The short version.

  • Formula: — the expected surprisal.
  • High entropy = high uncertainty (uniform); low entropy = concentrated; zero = deterministic.
  • Fair coin: bit. Biased 90/10 coin: bits. Deterministic: .
  • Bounds: for outcomes, using the convention .
  • Uses: decision trees (information gain), compression (a lower bound on code length), feature selection.

Why it works.

Where the formula comes from. Entropy is not an arbitrary functional; it is forced by requiring that a measure of information be additive over independent events. The information content, or surprisal, of an outcome with probability is : it is zero for a certain event, grows as the event gets rarer, and — critically — the surprisal of two independent events is , so it adds. Only the logarithm turns multiplication of probabilities into addition. Entropy is then just the expected surprisal, , which is what the formula says.

The operational meaning is the one to give in an interview. Shannon’s source coding theorem says in bits is the minimum average number of bits per symbol needed to encode messages from — no scheme does better, and schemes exist that get arbitrarily close. So entropy is not a metaphor for uncertainty; it is a compression bound.

Units. The base of the logarithm is only a unit choice: base 2 gives bits, base gives nats. Machine learning code uses nats because is the natural log; multiply by to convert.

The math, and what it buys you.

Running the biased coin gives the compression claim a number: bits — meaning a long sequence of such flips can be compressed to under half a bit per flip, which run-length encoding does in practice. The fair coin gives exactly 1 bit, and no scheme beats it.

The connection to language models. Perplexity, the standard LM metric, is exactly (or in nats) where is the cross-entropy per token. It is interpretable as an effective vocabulary size: a perplexity of 20 means the model is, on average, as uncertain as if it were choosing uniformly among 20 tokens. This is why perplexity 10 versus 20 is a much bigger improvement than the numbers suggest — it is one full bit of information per token.

Follow-up: Why is entropy used for decision-tree splits? A split is scored by information gain, : how much uncertainty about the label the split removes. It is always non-negative (conditioning cannot increase entropy on average), and it is zero exactly when the feature is independent of the label. Note the known failure mode: raw information gain favours high-cardinality features, since splitting on a unique ID gives pure children and maximal gain while generalising not at all — which is why C4.5 uses gain ratio, normalising by the entropy of the split itself.

Why the interviewer asks this. They usually want the compression interpretation, because it is the one that shows you understand entropy as a quantity with units rather than a vague synonym for disorder.

Saying it out loud. “Entropy is the expected surprise of a distribution — surprise being minus log p, which is the only form that makes independent events add up. The concrete meaning is compression: entropy in bits is the smallest average number of bits per symbol you can encode the source in. A fair coin is one bit and you can’t do better; a ninety-ten coin is under half a bit because you can exploit the skew. It’s maximised by the uniform distribution and zero when one outcome is certain. In language modelling it’s the same quantity — perplexity is just two to the entropy, so you can read it as an effective vocabulary size.”


Q24: What is cross-entropy? Why is it used as a loss function?

In 30 seconds. “Cross-entropy is the expected number of bits you pay to encode data from P using a code built for Q, and it exceeds the true entropy by exactly the KL divergence — so minimising it is minimising KL from the data to your model. It’s also just the negative log-likelihood, and it’s a proper scoring rule, meaning you minimise it by reporting honest probabilities rather than by being overconfident.”

The short version.

  • Formula: .
  • Penalises confident wrong predictions: — a large penalty, unbounded as .
  • Encourages calibrated probabilities: it is a strictly proper scoring rule.
  • Always entropy: , with equality only when . The gap is the KL divergence.
  • Smooth gradients, and the same object as negative log-likelihood.
  • Uses: classification (the default loss), language modelling, any probabilistic prediction.

Why it works.

Three derivations arrive at the same loss, which is the real reason to trust it.

From coding. is the average number of bits used if you build an optimal code for but the data actually comes from . It exceeds the true entropy by exactly — the wasted bits from being wrong. Since is fixed by the data, minimising cross-entropy is identical to minimising KL divergence from the true distribution to the model. That is the cleanest statement of what training a classifier does.

From maximum likelihood. The probability the model assigns to the whole dataset is . Taking logs turns the product into a sum, and negating turns maximisation into minimisation: . With one-hot labels this is exactly the cross-entropy. So cross-entropy loss is not a heuristic scoring function — it is the negative log-likelihood, and every guarantee that maximum likelihood carries comes with it.

From calibration. Cross-entropy is a strictly proper scoring rule, meaning its expected value is uniquely minimised when the model reports its true beliefs. If the real probability of the positive class is 0.7 and you report 0.9 to look decisive, your expected loss goes up. Accuracy is not proper — reporting 0.99 and reporting 0.51 score identically — which is precisely why you train on cross-entropy and only report accuracy.

The math, and what it buys you.

Worked numbers on the “penalises confident wrong predictions” claim. With the true label being class 1: predicting costs nats; costs ; costs ; costs . The loss grows without bound as , so a single confidently wrong prediction can dominate a whole batch. That unboundedness is a feature during training — it produces the large gradient that fixes the mistake — but it is also why numerically you must never compute separately (a rounded-to-zero probability gives ) and instead use the fused log_softmax / cross_entropy op, which subtracts the max logit before exponentiating.

Good and bad.

  • Good: identical to maximum likelihood, so it inherits its guarantees; proper, so it produces calibrated probabilities; and its gradient scales with how wrong you are.
  • Bad: unbounded, so a single mislabelled example can dominate a batch; numerically dangerous unless fused with the softmax; and dominated by the majority class under imbalance.

Follow-up: What if the classes are heavily imbalanced? Plain cross-entropy is dominated by the majority class simply because there are more of its terms. Class weighting multiplies each term by (often inversely proportional to class frequency) to rebalance. Focal loss goes further and multiplies by , which shrinks the contribution of already-well-classified examples so training focuses on the hard ones — it was designed for dense object detection where the background class outnumbers objects by a thousand to one. Both keep the log-likelihood structure and just reweight it.

Why the interviewer asks this. The good answer connects cross-entropy to KL divergence and to maximum likelihood, showing you see one object rather than three unrelated formulas.

Saying it out loud. “Cross-entropy is the expected number of bits you pay for encoding data from P using a code built for Q, and it beats the true entropy by exactly the KL divergence — so minimising it is minimising KL from the data distribution to your model. It’s also just the negative log-likelihood, so you get all the maximum-likelihood guarantees for free. And it’s a proper scoring rule, which means you minimise it by reporting your honest probabilities, not by being overconfident. The practical property is that the loss goes to infinity as your predicted probability for the true class goes to zero, so confidently wrong predictions produce big corrective gradients.”


Q25: Explain KL divergence. Why is it asymmetric?

In 30 seconds. “KL is an expectation under its first argument, and that single fact is the whole reason it’s asymmetric. Forward KL only looks where P has mass and blows up if your model puts near-zero probability there — so it’s mass-covering, and that’s what maximum likelihood minimises. Reverse KL punishes putting mass where P has none but happily ignores a whole mode — mode-seeking, which is what variational inference uses, and why VI underestimates posterior variance.”

The short version.

Forward KL Reverse KL
Expectation taken under (the truth) (the model)
BehaviourMass-covering / zero-avoidingMode-seeking / zero-forcing
Fitting one Gaussian to a bimodal PStretches to span both modes, mass in the empty valleyCollapses onto one mode, abandons the other
Where you meet itMaximum likelihoodVAE ELBO, variational inference, RLHF penalty

Properties:

  • , and if and only if .
  • Not a metric — it fails the triangle inequality, and it is not symmetric.
  • Uses: RLHF KL penalty, VAEs (posterior vs prior), model comparison, regularisation.

Why it works.

The formula is an expectation under . That single fact drives everything. Only the regions where is large contribute; regions where contribute nothing no matter what does there. And if where , the log blows up and the divergence goes to infinity. In one sentence: punishes for failing to cover , and is indifferent to putting mass where has none.

Now fit a single Gaussian to a bimodal and the two directions give visibly different answers:

  • Forward KL, — mass-covering / zero-avoiding. must be nonzero everywhere is, or it takes an infinite penalty. So the fitted Gaussian stretches wide to span both modes, placing most of its mass in the empty valley between them. This is the direction minimised by maximum likelihood, which is why a maximum-likelihood language model will assign some probability to almost everything.
  • Reverse KL, — mode-seeking / zero-forcing. Now the expectation is under , so is punished for putting mass where is small, but not for ignoring a mode entirely. The fitted Gaussian collapses onto one mode and abandons the other. This is the direction in the VAE’s ELBO and in variational inference generally, and it is the standard explanation for posterior-variance underestimation in VI and for mode collapse in some distillation setups.

The math, and what it buys you.

A numeric example that makes the asymmetry undeniable. Let and over three outcomes. Forward: nats. Reverse: , because puts mass on an outcome rules out. Same pair of distributions, radically different numbers — which is also a concrete demonstration that KL is not a distance and cannot be used where symmetry is assumed.

Why it is non-negative. By Jensen’s inequality applied to the convex function : , with equality only when everywhere. This result is Gibbs’ inequality, and it is what guarantees cross-entropy is always at least entropy.

Follow-up: If KL is infinite whenever supports mismatch, how is it used in practice? By construction, mostly. In RLHF the reference and policy are the same architecture over the same vocabulary with softmax outputs, so every token has nonzero probability under both and KL is finite. In VAEs both terms are Gaussians with full support and the KL has a closed form. Where support genuinely mismatches — comparing empirical samples from a generator against real data, the original GAN setting — KL and JS both become useless (constant or infinite, giving no gradient), which is exactly the motivation for the Wasserstein distance, which stays finite and differentiable for disjoint supports.

Why the interviewer asks this. “It’s asymmetric” is the memorised answer; the follow-up is always “so what?”, and mode-seeking versus mass-covering is the answer that shows you know when the choice of direction changes your model’s behaviour.

Saying it out loud. “KL is an expectation under the first argument, and that’s the whole reason it’s asymmetric. Forward KL, P given Q, only looks at places where P has mass, and it goes to infinity if your model puts near-zero probability there — so it’s mass-covering, it forces your model to spread out and cover everything. That’s what maximum likelihood minimises. Reverse KL is the opposite: it punishes you for putting mass where P doesn’t have any, but it’s perfectly happy to ignore a whole mode. So it’s mode-seeking, and that’s what variational inference uses — which is exactly why VI tends to underestimate posterior variance.”


Q26: What is mutual information? How is it used in feature selection?

In 30 seconds. “Mutual information is how much knowing one variable reduces your uncertainty about the other — equivalently, the KL divergence between the true joint and the product of the marginals, so it literally measures distance from independence. People reach for it over correlation because correlation only sees linear structure: if Y is X squared with X centred at zero, correlation is exactly zero and MI is maximal.”

The short version.

  • Formula: .
  • means independent; means dependent; means determines .
  • Feature selection: compute per feature, keep the high ones.
  • Why: captures non-linear relationships (unlike correlation), removes irrelevant features (), has an information-theoretic foundation.
  • Two failure modes: the plug-in estimator is biased upward with many bins, and ranking features one at a time misses interactions.

Why it works.

Three equivalent readings of the same quantity. . The first says “how much does knowing reduce my uncertainty about ”; the second is the same statement with the roles swapped, which is why MI is symmetric; the third is the inclusion-exclusion form given above. A fourth reading is the most useful theoretically: — mutual information is the KL divergence between the true joint and the joint you would have if they were independent. That immediately gives you (KL is non-negative) and exactly under independence.

One correction to the note above. The line “: X completely determines Y” has it backwards. means , i.e. knowing leaves no uncertainty about — so determines . In general , and it equals precisely when is a deterministic function of .

The math, and what it buys you.

Worked example against correlation — the one to have ready. Let be uniform on and . Pearson correlation is exactly 0 — the relationship is symmetric about zero, so the linear term cancels — yet is a deterministic function of , so , in fact maximal for that pair. A correlation-based filter discards this feature; a mutual-information filter keeps it. This makes “captures non-linear relationships” concrete instead of asserted.

How it is actually estimated, and why that matters. For discrete variables you count joint and marginal frequencies and plug in — but the plug-in estimator is biased upward, and the bias grows with the number of bins relative to the sample size. In the limit, a feature with as many distinct values as there are samples gets maximal apparent MI while carrying no signal at all, which is the same high-cardinality trap that afflicts decision-tree information gain. For continuous variables, binning is crude and bin-width-sensitive; the standard alternative is the Kraskov (KSG) -nearest-neighbour estimator, which is what sklearn.feature_selection.mutual_info_regression uses. Normalised variants — dividing by or by , or the adjusted mutual information — put values on a comparable scale and partially correct the cardinality bias.

Good and bad.

  • Good: model-agnostic, cheap as a first-pass filter over thousands of candidate features, sensitive to any dependence rather than only linear ones, and invariant to monotone reparameterisation.
  • Bad — the real limitation of MI feature selection. Ranking features by individual is a univariate filter: it evaluates each feature in isolation. That misses both directions of interaction. It keeps redundant features — ten copies of the same predictor all score high and all get selected. And it discards features that are only useful jointly: in an XOR relationship, while is maximal, so a univariate filter throws away both of the only useful features. The fix is a criterion that accounts for the already-selected set, such as mMRM (maximum relevance, minimum redundancy), which selects to maximise .
  • Bad: upward estimation bias with high-cardinality features.

Follow-up: When would you use MI over a model-based importance measure? When you want something model-agnostic and cheap as a first-pass filter over thousands of candidate features, or when you need a dependence measure that is invariant to monotone reparameterisation of the features. When you can afford it, wrapper or embedded methods — permutation importance, L1 paths, tree-based importances — usually select better because they account for feature interactions and for the specific model you intend to deploy.

Why the interviewer asks this. They want to see whether you know MI’s failure modes — estimation bias and the univariate blind spot — not just that it beats correlation on nonlinear data.

Saying it out loud. “Mutual information is how much knowing one variable reduces your uncertainty about the other. My favourite way to state it is that it’s the KL divergence between the true joint distribution and the product of the marginals — so it’s literally measuring how far the two are from independent. The reason people reach for it over correlation is that correlation only sees linear structure: if Y equals X squared with X centred at zero, correlation is exactly zero but mutual information is maximal. The catch is that estimating it is biased upward with lots of bins, and ranking features one at a time misses interactions — an XOR pair scores zero individually and everything jointly.”


Q27: Compare Gini impurity and entropy. When would you use each?

In 30 seconds. “They’re measuring the same thing and they’re almost the same curve — Taylor-expand entropy around a half and you get the Gini form back. Both are zero on a pure node, maximal when the classes are balanced, and they pick the same split the overwhelming majority of the time. If someone’s tuning Gini versus entropy they’re on the wrong knob: depth, minimum leaf size and high-cardinality bias matter far more.”

The short version.

Gini impurityEntropy
Formula
Binary form, peak 0.5 at , peak 1.0
AlgorithmCARTID3 / C4.5
CostFaster — no logarithmSlightly slower; irrelevant on modern hardware
Behaviour near purityFlatter; mild preference for one large mostly-pure childSteeper; slightly keener to isolate a small pure subgroup
In practiceNearly identical resultsNearly identical results

Why it works.

Why they behave alike — they are the same curve to first order. Both are concave functions of the class proportions, both are zero for a pure node and maximal at the uniform distribution, and both reward splits that produce purer children. At Gini peaks at 0.5 and entropy at 1.0; halve the entropy and the two curves agree to within about 0.03 everywhere on . In fact the first two terms of the Taylor expansion of entropy about give exactly the Gini form, which is the formal reason “results are usually very similar” — they are not similar by coincidence.

The one real difference. Entropy’s term goes to infinity as , so entropy is steeper near the pure ends of the range, which makes it slightly more willing to isolate a small pure subgroup. Gini, being quadratic, is flatter there and has a mild preference for splits that produce one large, mostly-pure child — it can be shown to favour balanced, high-purity partitions. In practice this is a second-order effect. The speed argument for Gini (no logarithm) was material in the 1980s and is largely irrelevant now that logs are a single instruction and split search is dominated by sorting.

The math, and what it buys you.

A worked comparison, showing the two criteria agree. A node with 80 positives and 20 negatives has Gini and entropy bits. Split it into a pure child of 60 positives and an impure child of 20 positives / 20 negatives. Weighted Gini afterwards is , a reduction of 0.12. Weighted entropy is , a reduction of 0.322 bits. Different units, same ranking — and that is the general pattern: published comparisons find the two criteria disagree on the chosen split in only a small minority of cases, and the resulting trees differ in accuracy by amounts well inside cross-validation noise.

Good and bad.

  • Gini — good: cheap, the CART default, mildly favours balanced high-purity partitions. Entropy — good: an information-theoretic reading (information gain), slightly better at peeling off small pure groups.
  • Bad, for both — what matters far more. Neither criterion addresses the actual failure mode of impurity-based splitting: bias toward high-cardinality features. A feature with many distinct values can carve out pure children by memorising, so it wins on both Gini and entropy while generalising not at all. C4.5’s gain ratio normalises information gain by the entropy of the split itself; CART restricts to binary splits, which limits the damage. If you are choosing between Gini and entropy you are tuning a knob that barely moves; if you are ignoring cardinality bias, tree depth, minimum leaf size, and the number of trees, you are ignoring the knobs that do.

Follow-up: What about regression trees? Impurity is replaced by variance (equivalently, mean squared error within the node): score a split by the weighted reduction in variance of the target. Mean absolute error is the robust alternative when outliers matter, at the cost of being slower to compute since the optimal constant becomes the median rather than the mean. Gradient boosting generalises this further by fitting each tree to the gradient of an arbitrary differentiable loss, so the splitting criterion is derived from the loss rather than fixed.

Why the interviewer asks this. Often to see whether you will claim a meaningful difference where there is not one — the strong answer is that they are nearly the same function and the real hyperparameters are elsewhere.

Saying it out loud. “They’re measuring the same thing and they’re almost the same curve — if you Taylor-expand entropy around a half you get the Gini form back. Both are zero on a pure node and maximal when the classes are balanced, and in practice they pick the same split the overwhelming majority of the time. Entropy is a bit steeper near purity so it’s slightly keener to peel off a small clean group; Gini is a bit cheaper because there’s no logarithm, though that hardly matters now. Honestly, if someone’s tuning Gini versus entropy they’re spending effort on the wrong knob — depth, minimum leaf size and high-cardinality bias matter far more.”


Q28: What is Jensen-Shannon divergence? How does it differ from KL?

In 30 seconds. “JS is the symmetrised KL: build the midpoint mixture of the two distributions and average the KL from each one to that midpoint. The mixture has mass wherever either distribution does, so you never divide by zero and the value is capped at log 2 — where KL between disjoint distributions is just infinity. The catch is that same cap: if two distributions barely overlap, JS pins at its maximum and the gradient goes flat, which is the standard story for why GANs were hard to train.”

The short version.

KL divergenceJS divergence
Symmetric?NoYes
Bounded?No — can be infiniteYes — capped at (1 bit)
Metric?NoNot itself; its square root is
Disjoint supportsInfinite, no gradientPinned at maximum, gradient ≈ 0
Use forMaximum likelihood, VI, RLHF penalty — where direction is the pointSymmetric comparison: drift monitoring, clustering comparison, GANs

Definition: , where .

One precision fix on the standard claims. JS divergence itself is bounded by (that is 1 when measured in bits, in nats) and is not a metric — it fails the triangle inequality. What is a true metric is its square root, , sometimes called the Jensen-Shannon distance; that is the quantity scipy.spatial.distance.jensenshannon returns. So the table should read: bounded by , and metric after taking the square root.

Why it works.

Why the mixture fixes KL’s blowup. The failure mode of KL is that whenever assigns zero probability somewhere does not. JS compares each distribution not to the other but to the midpoint , and is nonzero wherever either is nonzero. So the ratio can never exceed 2, the log never exceeds , and the whole quantity stays finite even for completely disjoint supports. Take the extreme case: and with no overlap at all. Then wherever we have , so , and likewise for , giving — the maximum. Contrast for the same pair.

The math, and what it buys you.

A worked example. With and : . Then nats and by symmetry , so nats (0.5 bits) — a finite, interpretable number, where forward KL between and is infinite in both directions. That finiteness is what JS buys you.

The GAN connection, and its punchline. Goodfellow’s original analysis showed that with an optimal discriminator, the generator’s objective reduces to minimising . That looks like good news until you notice the consequence of the boundedness argument above: early in training the generator’s output distribution and the real data distribution lie on nearly disjoint low-dimensional manifolds, so JS sits pinned at its maximum and its gradient is essentially zero. The generator gets no useful signal — this is the vanishing-gradient explanation for GAN training instability. Wasserstein GAN replaced JS with the earth-mover distance precisely because it varies smoothly with how far apart two disjoint distributions are, rather than saturating.

Good and bad.

  • JS — good: symmetric, always finite, bounded, interpretable, and stable when the two distributions are very different in shape.
  • JS — bad: saturates on nearly disjoint supports, so it provides no gradient exactly when you most need one; and it throws away the directional information that makes KL useful.
  • KL — good: the direction is the modelling choice (mass-covering vs mode-seeking), and it has closed forms in the Gaussian cases you meet in practice. Bad: infinite on support mismatch.

Follow-up: When would you still prefer KL? Whenever the asymmetry is the point. Maximum-likelihood training is forward KL and you want its mass-covering behaviour; variational inference is reverse KL and you want its mode-seeking behaviour; the RLHF penalty is one-directional because “how far has the policy moved from the reference” is inherently a directed question. Reach for JS when you need a symmetric, bounded comparison — measuring drift between two data distributions in monitoring, comparing two clusterings, or any case where you would otherwise be embarrassed to report a value of infinity.

Why the interviewer asks this. Usually to get to the GAN story: knowing that JS saturates on disjoint supports, and that this is why WGAN exists, is what the question is really reaching for.

Saying it out loud. “JS is the symmetrised version of KL — you build the midpoint mixture of the two distributions and average the KL from each one to that midpoint. The reason that helps is that the mixture has mass wherever either distribution does, so you can never divide by zero and the value is capped at log 2. KL between disjoint distributions is just infinity, which is useless. The catch is that capped behaviour: if two distributions barely overlap, JS is pinned at its maximum and the gradient is flat. That’s the standard explanation for why GANs were so hard to train and why Wasserstein distance replaced it.”


See 33_information_theory/interview_qa.md for even more detailed answers!


Discriminative vs Generative Models

Q29: Explain the difference between discriminative and generative models.

In 30 seconds. “A discriminative model learns the conditional — given the input, what’s the label — so it only models what separates the classes. A generative model learns the joint, which means it has to describe what the data itself looks like. That’s strictly more work, so with limited data the generative model’s assumptions act as a prior and it wins; with plenty of data the discriminative model wins because it isn’t spending capacity on the input distribution.”

The short version.

AspectDiscriminativeGenerative
What they learnP(Y|X)P(X, Y) = P(X|Y)P(Y)
Can generate dataNoYes
Data efficiencyMore efficientLess efficient
ComplexitySimplerMore complex
Use casePredictionGeneration + Prediction
ExamplesLogistic regression, SVM, neural nets, decision treesNaive Bayes, GMM, GANs, VAEs, language models
Missing featuresMust imputeMarginalise out

When to use:

  • Discriminative: When you only need predictions, have limited data.
  • Generative: When you need to generate data, have missing data, or want to understand the distribution.

Why it works.

The real dividing line is what the model spends its capacity on. A discriminative model only ever has to answer “given this input, which label?”, so it can ignore everything about the input that does not separate classes. A generative model has to account for the input itself — requires describing what the data looks like — which is strictly more information and usually a much harder estimation problem.

The math, and what it buys you.

The canonical worked contrast: naive Bayes versus logistic regression. These two are a generative-discriminative pair: with the same features and a matched parametric form, they define the same hypothesis class of linear decision boundaries but fit it differently. Naive Bayes estimates and by counting, assuming features are conditionally independent given the label, then applies Bayes’ rule. Logistic regression parameterises directly and maximises conditional likelihood. Ng and Jordan’s result on this pair is the fact worth citing: naive Bayes converges to its (higher) asymptotic error much faster — in samples versus for logistic regression — so the generative model wins on small data and the discriminative model wins once there is enough data, with a crossover point. That is the mechanism behind the “data efficiency” row in the table, and it is more useful than the row itself: the generative model’s assumptions act as a strong prior, which helps when data is scarce and hurts when the assumptions are wrong and data is plentiful.

Good and bad.

  • Discriminative — good: spends all capacity on the decision boundary; better asymptotic accuracy; simpler estimation. Bad: cannot sample, cannot marginalise over missing features, and will confidently classify pure noise.
  • Generative — good: can generate, can marginalise, gives you so anomaly detection is a quantity it can report; strong prior helps in low-data regimes. Bad: must model the input distribution, which is harder and wastes capacity if the assumptions are wrong.

A caution about modern usage. The table’s “generative models are more complex” is a statement about classical estimation, and today’s usage of the word has drifted. A modern autoregressive language model is generative in the strict sense — it models , factorised as — and it is spectacularly effective, not handicapped. What changed is that modelling over text turned out to be a rich enough task that solving it produces general capability, and that conditioning () can be obtained from the same model by prompting. So “generative is less data efficient for classification” is still true when comparing matched pairs on a fixed task, and simultaneously the most capable classifiers available today are generative models used zero-shot. Both are true; they are answers to different questions.

Follow-up: Which family handles missing features better, and why? Generative. Because it models the joint, a missing feature can be marginalised out — integrate over the unobserved variable and carry on with a properly normalised answer. A discriminative model has no distribution over to integrate against, so it must impute the missing value first and then hope the imputation error does not move the decision boundary. Anomaly detection is the same argument: a generative model gives you directly, so “this input is unlike anything I was trained on” is a quantity it can report, whereas a discriminative model will confidently assign a class to pure noise.

Why the interviewer asks this. It is a check on whether you can name a consequence of the distinction — data efficiency, missing values, anomaly detection — rather than only restating the two formulas.

Saying it out loud. “A discriminative model learns the conditional — given the input, what’s the label — so it only has to model what separates the classes. A generative model learns the joint, which means it has to describe what the data itself looks like. That’s strictly more work, so with limited data the generative model’s assumptions act as a prior and it does better, but once you have plenty of data the discriminative model wins because it isn’t wasting capacity on the input distribution. Naive Bayes versus logistic regression is the classic pair. The nice side effect of going generative is that you can sample, marginalise over missing features, and score how unusual an input is.”


Q30: What are the assumptions of linear regression?

In 30 seconds. “I’d group them by what they protect. Linearity and errors uncorrelated with the predictors are what make the coefficients unbiased — if those fail, more data won’t save you. Homoscedasticity and independence don’t bias the coefficients, they bias your standard errors. Normality only matters for small-sample inference. And multicollinearity isn’t really a model assumption at all — it doesn’t hurt prediction, it just makes individual coefficients unstable.”

The short version.

AssumptionWhat it protectsHow to checkFix
1. LinearityUnbiasedness of Residuals vs predicted (should be random)Polynomial features, transformations
2. Independence of errorsCorrect standard errorsDurbin-Watson, residuals vs timeTime-series models; clustered SEs
3. HomoscedasticityCorrect standard errorsResiduals vs predicted (look for a funnel)Weighted least squares, robust (HC3) SEs
4. Normality of errorsExact small-sample inference onlyQ-Q plot, Shapiro-WilkTransformations; fades as grows
5. No multicollinearityInterpretability of single coefficients — not predictionCorrelation matrix, VIFDrop features, regularise

What happens if violated: poor predictions, wrong standard errors, unreliable tests. See 34_discriminative_generative/model_assumptions_detailed.md for detailed explanations.

Why it works.

The five assumptions are not equally important, and they do not all protect the same thing. Split them:

Needed for the coefficient estimates to be unbiased: linearity and exogeneity (errors uncorrelated with the predictors). If these fail, is estimating the wrong thing and no amount of data fixes it. This is the serious one — omitted variable bias lives here.

Needed for the standard errors, p-values and confidence intervals to be correct: homoscedasticity and independence of errors. If these fail, is still unbiased, but your uncertainty estimates are wrong — typically too small, so you declare significance that is not there. The fix is often not to change the model but to change the variance estimator: heteroscedasticity-consistent (Huber-White, HC3) standard errors, or clustered standard errors for grouped data.

Needed only for exact small-sample inference: normality of errors. By the central limit theorem the sampling distribution of is approximately normal for large regardless of the error distribution, so this assumption fades as grows. Note also what it is not: nothing requires or to be normally distributed — only the residuals, and only for inference.

Not an assumption of the model at all: no multicollinearity. Perfect collinearity makes singular so the solution is not unique; severe-but-imperfect collinearity leaves predictions and completely fine and only inflates the variance of individual coefficients. So if you care about prediction, collinearity is close to a non-issue; if you care about interpreting a specific coefficient, it is fatal.

Why residual plots are the single most useful diagnostic. Plot residuals against fitted values and you can see three assumptions at once: curvature indicates a linearity violation, a funnel shape indicates heteroscedasticity, and clusters or drift indicate dependence. It is worth remembering Anscombe’s quartet here — four datasets with identical means, variances, correlations and regression lines but completely different structure, visible instantly in a plot and invisible in the summary statistics.

The math, and what it buys you.

The variance inflation factor puts a number on how much collinearity costs you:

where is from regressing feature on the others. A VIF of 10 means that coefficient’s standard error is times larger than it would be with uncorrelated features — which tells you directly whether the instability is bad enough to act on.

Follow-up: Do these assumptions apply to ridge and lasso? The estimation assumptions do not carry over cleanly, because both are deliberately biased estimators — the whole point of regularisation is to trade bias for variance, so unbiasedness was never on offer. Linearity still matters for the model to be right. Inference is genuinely harder: the standard -tests do not apply to lasso coefficients, since the selection step is data-dependent, which is why post-selection inference is its own research area. In practice people using regularised regression for prediction check linearity and residual structure and skip the inference machinery entirely.

Why the interviewer asks this. Listing five assumptions is memorisation; saying which ones break unbiasedness versus which only break the standard errors is understanding.

Saying it out loud. “I’d group them by what they protect. Linearity and having errors uncorrelated with your predictors are what make the coefficients unbiased — if those fail, your estimates are wrong and more data won’t save you. Homoscedasticity and independent errors don’t bias the coefficients, they bias your standard errors, so you’d get significance you haven’t earned; often the fix is just robust or clustered standard errors rather than a different model. Normality only matters for small-sample inference and washes out with the central limit theorem. And multicollinearity isn’t really a model assumption at all — it doesn’t hurt prediction, it just makes individual coefficients unstable and uninterpretable.”


Q31: What are the assumptions of logistic regression?

In 30 seconds. “The load-bearing assumption is that the log-odds are linear in the features — not the probability. So anything genuinely non-monotone, like risk being high at both extremes of a measurement, can’t be represented without extra terms. You also need independent observations and enough events, not just rows. And the one that bites in practice is complete separation: coefficients run to infinity and the fit doesn’t converge — a little L2 fixes it.”

The short version.

  1. Binary outcome. ; use multinomial logistic for multi-class.
  2. Linearity of log-odds. Check with the Box-Tidwell test; fix with polynomial terms, splines or interactions.
  3. Independence of observations; use mixed-effects models for correlated data.
  4. No multicollinearity — same story as linear regression.
  5. Enough data, and specifically enough events: 10-20 per predictor.

Differences from linear regression:

  • No normality assumption (errors are binary).
  • No homoscedasticity (variance is by construction).
  • The probability is sigmoid — non-linear — not linear.

Why it works.

Linearity of the log-odds is the assumption that actually does the work. The model is , so it is a linear model in log-odds space. On the probability scale the same relationship is S-shaped, which is why “logistic regression is a linear model” confuses people — it is linear in the link, not in the output. The practical consequence: a genuinely non-monotone relationship (risk high at both low and high values of a feature, as with blood pressure or dosage) cannot be captured, and no amount of data will reveal it. You have to add the quadratic term, spline the feature, or bin it.

Complete separation is a failure mode worth naming. If some hyperplane perfectly separates the classes, the likelihood is maximised by pushing the coefficients to infinity — every step further increases the fitted probabilities toward 0 and 1 and keeps reducing the loss. There is no finite MLE. Symptoms are enormous coefficients, standard errors in the thousands, and non-convergence warnings. This happens routinely with small samples, rare outcomes, or a feature that encodes the label. The standard remedies are exactly the regularisers you would use anyway — an L2 penalty makes the objective strictly convex and guarantees a finite solution, which is why sklearn’s LogisticRegression applies L2 by default — or Firth’s penalised likelihood in a statistics setting.

On the “large sample size” rule. The real constraint is the number of events, not the number of rows. The classical rule of thumb is 10-20 events per predictor variable (EPV): with 1,000 rows but only 30 positives, you can support two or three predictors, not fifty. This is why rare-outcome problems are hard even with large datasets.

Follow-up: How do you read a fitted coefficient? is the change in log-odds per unit change in feature , holding the others fixed, so is the odds ratio — a multiplicative effect on the odds. A coefficient of 0.7 means , so the odds double per unit. Two cautions worth voicing: odds ratios are not risk ratios and diverge badly when the base rate is high (doubling odds from 0.9 raises probability only from 0.47 to 0.64), and the effect on probability is not constant — it is largest near and near zero out in the tails, because that is where the sigmoid is steep.

Why the interviewer asks this. Mostly to see whether you know what “linear” refers to in logistic regression, and whether you have ever met separation in a real fit.

Saying it out loud. “The load-bearing assumption is that the log-odds are linear in the features — not the probability, the log-odds. So the relationship on the probability scale is S-shaped, and anything genuinely non-monotone, like risk being high at both extremes of a measurement, just can’t be represented without adding terms. You also need independent observations and you need enough events, not just enough rows — the rule of thumb is ten to twenty positive cases per predictor. And the one that bites in practice is complete separation: if a feature perfectly splits the classes the coefficients run off to infinity and the fit doesn’t converge. A little L2 fixes it, which is why sklearn regularises by default.”


Q32: What are the assumptions of SVM?

In 30 seconds. “The main one is feature scaling, and it isn’t optional — the SVM maximises a margin measured as Euclidean distance, so whichever feature has the biggest numeric range dominates the geometry, and with an RBF kernel that’s even more true. Beyond that they’re really choices rather than assumptions: pick a kernel that matches the structure, tune C and gamma together because they interact, and watch class imbalance since the margin doesn’t know one class matters more.”

The short version.

  1. Separable data. Hard-margin needs linear separability; soft-margin tolerates violations. Fix: kernel, or allow margin violations.
  2. Feature scaling — critical. SVM is very sensitive to scale. Always StandardScaler or MinMaxScaler.
  3. Appropriate kernel. Linear for linearly separable; RBF for non-linear local structure; polynomial for polynomial relationships. Cross-validate.
  4. Balanced classes. Sensitive to imbalance; use class weights, SMOTE, cost-sensitive learning.

What SVM does not assume: normal distributions, linear relationships (with kernels), or a large sample size.

Where the “assumptions” framing is loose. SVM is not a probabilistic model, so it has no likelihood and therefore no distributional assumptions in the sense that linear regression has them. What it has are requirements for the geometry to be meaningful (scaling), a modelling choice that must match the data (the kernel), and a known sensitivity (class imbalance, because the margin term does not know that one class matters more; class_weight='balanced' rescales per class to fix it). It is worth saying this explicitly in an interview, because it shows you know why the question is phrased differently for SVM than for regression.

Why it works.

Why feature scaling is not a preference but a consequence of the objective. The linear SVM maximises the margin, and the margin is measured in Euclidean distance in feature space. So the geometry of the answer depends directly on the units of your features. If income is in dollars (range ) and age is in years (range 20–80), a one-unit change in age is geometrically negligible next to a one-unit change in income, so the maximum-margin hyperplane will be almost entirely determined by income regardless of which feature is actually informative. The RBF kernel makes it worse: is dominated by whichever feature has the largest numeric spread, so the other features effectively vanish from the similarity computation. This is why scaling is marked “critical” — it is not a tuning nicety, it is required for the objective to mean what you intend.

What “support vector” means and why it matters. Only the points on or inside the margin have nonzero dual coefficients; the solution depends on those alone. Delete every other training point and refit and you get the identical boundary. That is a genuinely unusual property — it makes SVM robust to far-away outliers of the correct class, but it also means a single mislabelled point sitting inside the opposite class’s region becomes a support vector and can move the boundary substantially, especially with large .

The math, and what it buys you.

The regularisation parameter C, which the assumption list omits. Soft-margin SVM minimises

where are slack variables measuring how far each point violates its margin. is the exchange rate between a wide margin and few violations. Small means violations are cheap, so the margin grows, more points end up as support vectors, and the model is smoother and higher-bias. Large means violations are expensive, so the boundary contorts to classify training points correctly — low bias, high variance, and in the limit you recover the hard-margin SVM, which does not exist for non-separable data. and interact strongly, which is why they are always tuned jointly on a 2-D grid rather than one at a time.

Good and bad.

  • Good: strong margins generalise well in high dimensions; the solution depends only on the support vectors, so far-away correct points are irrelevant; kernels give non-linearity without explicit feature maps.
  • Bad: requires scaling to be meaningful at all; no probabilities out of the box; sensitive to class imbalance and to mislabelled points near the boundary; and / tuning is a 2-D search.

Follow-up: SVM does not output probabilities — what do you do if you need them? The decision function is a signed distance from the hyperplane, not a probability. Platt scaling fits a one-dimensional logistic regression to those distances on held-out data, which is what probability=True triggers in scikit-learn — note it runs an internal cross-validation, so it is slow and can produce probabilities that disagree with predict. Isotonic regression is the non-parametric alternative and fits better when you have enough calibration data. If probabilities are central to your problem, though, logistic regression or a gradient-boosted model is usually the better starting point.

Why the interviewer asks this. Scaling is the answer they are fishing for; the good candidate explains why the objective demands it rather than just asserting it.

Saying it out loud. “The main one is feature scaling, and it’s not optional — the SVM maximises a margin measured as Euclidean distance, so whichever feature has the biggest numeric range dominates the geometry. With an RBF kernel that’s even more true, because the distance in the exponent is driven by the largest-scale feature. Beyond that it’s really about choices rather than assumptions: pick a kernel that matches the structure, tune C and gamma together because they interact, and watch out for class imbalance since the margin doesn’t know one class matters more. And it’s not probabilistic, so if you need calibrated probabilities you have to bolt on Platt scaling afterwards.”


Q33: Explain Bayes’ theorem in detail.

In 30 seconds. “Bayes’ theorem is how you update a belief when evidence arrives — posterior proportional to likelihood times prior. My go-to example is the medical test: one percent prevalence, ninety-five percent accurate, and a positive result still means only about a sixteen percent chance you’re sick, because the false positives from the huge healthy group swamp the true positives. The odds form makes it even quicker: prior odds times the likelihood ratio.”

The short version.

P(A|B) = P(B|A) * P(A) / P(B)
  • Prior P(A): belief about A before seeing evidence.
  • Likelihood P(B|A): probability of the evidence given A.
  • Evidence P(B): total probability of B — where all the computational work lives.
  • Posterior P(A|B): updated belief after seeing B.
  • The headline example: 1% prevalence, 95% accurate test, positive result → only ~16% chance of disease, because of false positives from the large healthy population.
  • Uses: naive Bayes, spam detection, medical diagnosis, recommendation systems.

See 34_discriminative_generative/bayes_theorem_detailed.md for comprehensive explanation!

Why it works.

Why the denominator is where the work is. is computed by the law of total probability, , summing over every hypothesis. For a handful of discrete hypotheses this is arithmetic. For continuous parameters it becomes an integral over the whole parameter space that is usually intractable, and that single fact is the reason Bayesian computation exists as a field: MCMC samples from the posterior without ever evaluating the denominator (Metropolis-Hastings only needs ratios, in which it cancels), and variational inference sidesteps it by optimising a bound instead.

How naive Bayes uses this. Classify by — the denominator is dropped because it is the same for every class and cannot change the argmax. The “naive” part is the product: assuming features are conditionally independent given the class. That assumption is essentially always false in text (words are correlated), yet the classifier works well, because the argmax only needs the class ranking to be right, not the probabilities. The probabilities themselves come out wildly overconfident — typically saturated at 0 or 1 — which is why naive Bayes should not be trusted for calibrated output.

The math, and what it buys you.

Working the medical example all the way through, because the number is the point. Let be “has the disease” and be “tests positive”. Given prevalence , sensitivity , and specificity (so the false-positive rate is ):

The intuition is easier in counts. Test 10,000 people: 100 have the disease and 95 of them test positive; 9,900 do not and 5% of them — 495 people — test positive anyway. So 590 positive tests, of which 95 are real: 16%. The false positives outnumber the true positives five to one purely because the healthy group is 99 times larger. This is base rate neglect, and the counting version is the one to use in an interview because it makes the result feel obvious rather than paradoxical.

The odds form, which is faster and more illuminating. Divide the posterior for by the posterior for and the evidence term cancels:

Here: prior odds , likelihood ratio , so posterior odds , which is . Same answer with no denominator to compute. This form also makes sequential updating trivial — a second independent positive test multiplies by 19 again, giving odds , or 78% — and it shows exactly what evidence is: a multiplier on the odds, with strength given by the likelihood ratio.

Follow-up: What happens to the posterior as evidence accumulates? Under mild conditions the likelihood dominates and the posterior concentrates on the truth regardless of the prior — this is the Bernstein-von Mises theorem, and it is why prior choice matters most when data is scarce. The important exception is a prior that assigns exactly zero probability to some hypothesis: multiplying by zero stays zero forever, so no amount of evidence can recover it. Cromwell’s rule — never assign a prior of exactly 0 or 1 to anything you are not logically certain of — is the practical statement, and it is the same reason Laplace smoothing (Q40) exists in naive Bayes.

Why the interviewer asks this. The disease example is a base-rate test, and they want to see whether you can produce the 16% and explain it in counts rather than just recite the formula.

Saying it out loud. “Bayes’ theorem is just how you update a belief when evidence arrives — posterior is proportional to likelihood times prior. The example I always use is the medical test: one percent prevalence, ninety-five percent accurate, and a positive result only means about a sixteen percent chance you’re sick. The easiest way to see why is in counts. Out of ten thousand people, a hundred are sick and ninety-five of them test positive, but of the ninety-nine hundred healthy people, five percent — that’s four hundred and ninety-five — also test positive. So the false positives swamp the true ones, purely because the healthy group is so much bigger. The odds form is even quicker: prior odds times the likelihood ratio.”


See 34_discriminative_generative/model_assumptions_detailed.md for detailed assumption explanations!


Kernel Functions

Q34: What is a kernel function? Explain the kernel trick.

In 30 seconds. “A kernel gives you the dot product of two points in a high-dimensional feature space without ever building that space. Expand in two dimensions and you get exactly the dot product of a three-dimensional feature map. It works because algorithms like SVMs only ever touch the data through dot products, so you just swap the kernel in. The price is an Gram matrix, so it doesn’t scale past about a hundred thousand samples.”

The short version.

  • Definition: — the dot product in a transformed space, computed without the transform.
  • The problem it solves: explicitly transforming to high dimensions is expensive; the kernel computes the dot product directly.
  • Example (degree-2 polynomial): without the trick, build (8 dimensions); with the trick, just compute — same result, far faster.
  • Why it works: SVM and friends only need dot products, never the features themselves.
  • Validity (Mercer’s condition): must be symmetric and positive semi-definite.
  • Cost: in samples rather than in feature dimension.

Why it works.

Why “algorithms only need dot products” is not a coincidence. The SVM’s dual formulation is and prediction is . The inputs appear only inside dot products, in both training and prediction. So replacing each with gives you the same algorithm operating in the feature space, without ever being formed. The Representer Theorem is the general statement: for a broad class of regularised problems, the optimal solution lies in the span of the training points, so it can always be written using kernel evaluations alone. The same substitution turns PCA into kernel PCA, ridge regression into kernel ridge regression, and so on.

What makes a function a valid kernel. Mercer’s condition: must be symmetric and positive semi-definite, meaning the Gram matrix has no negative eigenvalues for any finite set of points. That is exactly the condition under which some exists with , and it is also what keeps the SVM’s dual problem convex. Kernels compose: sums, positive scalings, and products of valid kernels are valid, which is how structured kernels for text, graphs and time series get built.

The math, and what it buys you.

Doing the polynomial expansion explicitly is what makes the trick concrete — it shows the feature map you never had to build. Take two-dimensional inputs and :

with . So a 3-dimensional feature map falls out, and the on the cross term is not decorative — it is exactly what makes the dot product match. The inhomogeneous version gives six features, adding , which is where a count of “8” for the degree-2 map would come from only in three input dimensions. (In general, degree over input dimensions gives features, so a degree-4 kernel on 100 features corresponds to about 4.6 million explicit dimensions — computed by one dot product and one exponentiation.)

The RBF kernel makes the point unanswerably: corresponds to an infinite-dimensional feature map (expand the exponential as a power series and every polynomial degree appears). You could never write down, yet the kernel evaluates in a few floating-point operations.

Good and bad.

  • Good: non-linearity at the cost of one function evaluation; works on data with no natural vector representation; convexity preserved under Mercer’s condition.
  • Bad — the cost you are paying. The trick converts an problem in explicit feature dimension into an problem in the number of samples, because the Gram matrix is . That is a spectacular win when and a disaster when is large — kernel SVMs are impractical past roughly samples, since the matrix alone would need tens of gigabytes. Random Fourier features invert the trick for exactly this reason: they approximate the RBF kernel with an explicit low-dimensional random map, trading a little accuracy for linear-time training.

Follow-up: Is the kernel trick relevant in deep learning? Directly, rarely — neural networks learn their feature map rather than fixing it, which is the whole advantage. Theoretically, very much so: an infinitely wide network trained by gradient descent behaves as kernel regression under the Neural Tangent Kernel, which is one of the main tools for analysing why overparameterised networks generalise. And attention itself is often described as a kernel: is a similarity function determining how much each value contributes, and linear-attention methods work precisely by replacing that softmax kernel with a factorisable feature map to escape the quadratic cost.

Why the interviewer asks this. They want to see the explicit expansion, or at least that you know one exists — being able to write for the degree-2 case is the difference between having understood the trick and having heard of it.

Saying it out loud. “A kernel is a function that gives you the dot product of two points in some high-dimensional feature space without ever building that space. The concrete version: take x dot y squared in two dimensions and expand it — you get x-one squared, root-two x-one x-two, and x-two squared, so it’s exactly the dot product of a three-dimensional feature map. The RBF kernel corresponds to an infinite-dimensional map, and you still evaluate it with one subtraction and one exponential. It works because algorithms like SVMs only ever touch the data through dot products, in training and at prediction time, so you just swap in the kernel. The price is that you’re now working with an n-by-n Gram matrix, so it doesn’t scale past a hundred thousand samples or so.”


Q35: Explain different types of kernels. When would you use each?

In 30 seconds. “I think of a kernel as a statement about what ‘similar’ means, so picking one is picking a prior. Linear says similarity is directional alignment and is right when the data is already high-dimensional. RBF says similarity decays with distance — a local prior and a sensible default. Polynomial builds explicit feature interactions. Sigmoid isn’t even positive semi-definite for most settings, so I’d skip it.”

The short version.

KernelFormulaSimilarity it encodesUse when
LinearDirectional alignmentLinearly separable, high-dimensional data — e.g. TF-IDF text
PolynomialConjunctions of features up to degree Polynomial relationships, moderate non-linearity — e.g. circular boundaries at
RBFDecay with distance (local)The default for non-linear problems
SigmoidLoose neural-net analogyRarely — RBF is better, and this one breaks Mercer

Selection: try linear first; if that fails, RBF; if RBF overfits, try polynomial.

Why it works.

A kernel is a similarity function, and choosing one is choosing a prior over what “similar” means — so the right framing is not “which is most powerful” but “which notion of similarity matches my data”.

The linear kernel says similarity is alignment of directions: two documents are similar if they share weighted vocabulary. It is the right choice when the data is already high-dimensional enough to be separable as it is, which is why it is standard for TF-IDF text — with 50,000 features and 5,000 documents, the data is almost certainly linearly separable and any extra flexibility only buys overfitting. It is also the only kernel whose weights you can inspect directly, since lives in the original feature space; every other kernel gives you a solution expressed in terms of support vectors instead.

The polynomial kernel says similarity comes from conjunctions of features up to degree — it explicitly constructs interaction terms. That is a good match when you believe the label depends on products of features (a pixel pair, a feature interaction in tabular data). Its practical weakness is numerical: either explodes or vanishes as grows, so it is finicky to tune and rarely worth going past degree 3.

The RBF (Gaussian) kernel says similarity decays with distance, full stop. This is a local prior: nearby points should share labels and distant points are uninformative, with setting the scale of “nearby”. Because it can approximate any continuous decision boundary given enough support vectors, it is the sensible default when you have no structural belief about the data. Note it is stationary — depends only on , not on where in the space you are — so it assumes the same length scale applies everywhere.

The sigmoid kernel is worth knowing mainly for the reason to avoid it: it is not positive semi-definite for most parameter settings, so it violates Mercer’s condition, the dual problem stops being convex, and the solver may return something that is not a global optimum. Its historical appeal was a loose analogy to a two-layer neural network. RBF dominates it on essentially every benchmark.

The math, and what it buys you.

A decision rule with actual numbers. Compare the number of features to the number of samples . If (text, genomics, any wide sparse problem), use linear — the data is likely already separable and a nonlinear kernel adds variance for nothing. If (a few dozen dense features, tens of thousands of rows), the data probably needs curvature, so use RBF. If is very large, above , drop kernels altogether: the Gram matrix makes them infeasible, so use a linear SVM with a solver like LIBLINEAR, random Fourier features, or a gradient-boosted tree ensemble.

Follow-up: Can you build a kernel for non-vector data? Yes, and this is one of the kernel method’s real advantages. Because a kernel only needs to be a symmetric positive semi-definite similarity, you can define one directly on strings (the string kernel counts shared subsequences), on graphs (the Weisfeiler-Lehman kernel compares iteratively refined neighbourhood labels), or on sets and trees. That lets you run SVM, PCA or ridge regression on objects that have no natural vector representation at all — historically a large part of why kernel methods mattered in bioinformatics and NLP before deep learning learned representations instead.

Why the interviewer asks this. To hear whether you pick kernels by reasoning about the data’s shape, or by working down a fixed list.

Saying it out loud. “I think of a kernel as a statement about what ‘similar’ means, so picking one is picking a prior. Linear says similarity is directional alignment, and it’s right when your data’s already high-dimensional — text with TF-IDF is nearly always linearly separable, so anything fancier just overfits. Polynomial builds explicit feature interactions, which helps if you think the label depends on products of features, but it’s numerically awkward past degree three. RBF says similarity decays with distance, which is a local prior and a sensible default when you’ve got no strong structural belief. Sigmoid isn’t even positive semi-definite for most settings, so the optimisation isn’t guaranteed convex — I’d skip it.”


Q36: Explain RBF kernel in detail. How does gamma affect it?

In 30 seconds. “The RBF kernel is similarity that decays with distance, and gamma is an inverse length scale — it sets how far apart two points can be and still count as similar. Tiny gamma and everything looks similar to everything, so you underfit to a constant; huge gamma and every point is similar only to itself, so you memorise the training set. You want gamma where typical pairs sit in the middle, and you tune it jointly with C.”

The short version.

  • Formula: — close points similar, far points dissimilar.
  • Low (0.001): wide kernel → simpler boundary, risk of underfitting.
  • Medium (0.1–1.0): balanced, a good starting point.
  • High (10.0): narrow kernel → complex boundary, risk of overfitting.
  • Visual: each point creates a bump. Low gamma = wide bumps (simple); high gamma = narrow bumps (complex).
  • Tuning: start at , then grid search — jointly with .

Why it works.

Write the kernel as and you can read off : gamma is an inverse squared length scale, so is the distance over which similarity meaningfully decays. That conversion turns an abstract hyperparameter into something you can sanity-check against your data.

How gamma and C interact, which is what people get wrong. They are not independent knobs, because both control effective complexity. Large with large is the classic overfitting corner: narrow bumps and an insistence that every training point be classified correctly, producing islands of one class around individual points. Small with small underfits from both directions. There are also compensating diagonals — moderate with large can behave much like large with moderate — which is precisely why a joint 2-D grid finds good regions that two sequential 1-D searches miss.

The math, and what it buys you.

Putting numbers on gamma turns it from a mystery dial into a distance. Take two points at Euclidean distance 1 after standardisation:

at distance 1 at distance 2 at distance 3effective
0.010.9900.9610.9147.07
0.10.9050.6700.4072.24
1.00.3680.0180.000120.71
10.00.000045~0~00.22

At every point in a standardised dataset is similar to every other point — the kernel matrix is nearly all ones, the model has almost no ability to distinguish anything, and it underfits toward a constant. At a point is similar only to itself: the Gram matrix approaches the identity, every training point becomes its own support vector, training accuracy hits 100%, and test accuracy collapses. The useful range sits where at typical inter-point distances is neither near 0 nor near 1 — which is exactly what the default heuristic targets.

The scikit-learn defaults, decoded. gamma='scale' sets where is the number of features. The reasoning: for standardised data the expected squared distance between two random points is about , so this choice puts the exponent at roughly for a typical pair, landing near — squarely in the responsive region and, crucially, invariant to how many features you have and what scale they are on. The older gamma='auto' used , which ignores variance and is why it was replaced. Either way, grid search on a logarithmic scale around that value, jointly with .

Follow-up: Why is the RBF kernel called universal? Because the function class it induces is dense in the space of continuous functions on a compact domain: with enough support vectors and a suitable , an RBF SVM can approximate any continuous decision boundary to arbitrary accuracy. That is the theoretical reason it is the default choice. It also explains why regularisation is not optional here — a model class that can represent anything will represent your noise if you let it, so and are doing all the work of controlling capacity.

Why the interviewer asks this. Gamma is the hyperparameter people tune blindly; being able to convert it into a distance scale and predict what happens at the extremes shows you understand what you are tuning.

Saying it out loud. “The RBF kernel is similarity that decays with distance, and gamma is an inverse length scale — it sets how far away two points can be and still count as similar. If gamma’s tiny, everything looks similar to everything, the kernel matrix is basically all ones and you underfit to a constant. If gamma’s huge, every point is only similar to itself, the matrix goes to the identity, and you memorise the training set. So I want gamma where the similarity between typical pairs of points is somewhere in the middle, which is exactly what sklearn’s ‘scale’ default is doing — one over features times variance. And I’d always tune it jointly with C on a log grid, because both control complexity and they trade off against each other.”


Q37: How do you choose the right kernel?

In 30 seconds. “I start from the shape of the data. If features are comparable to or more numerous than samples — text, genomics, anything wide and sparse — linear is almost always right, because the data is likely separable already. Few dense features and lots of rows, reach for RBF and grid-search C and gamma together on a log scale. Then read the learning curve rather than guessing. And on tabular data of any size, check gradient-boosted trees first.”

The short version.

Decision process:

  1. Try linear first: fast, interpretable.
  2. If it fails, try RBF: the default for non-linear.
  3. If RBF overfits, try polynomial: less flexible.
  4. Never use sigmoid: RBF is better.

Parameter tuning:

  • RBF: gamma over and C over — jointly.
  • Polynomial: start with degree 2, gamma 1.0.
  • Always cross-validate across kernels and parameters.

Key points: always scale features before an SVM; linear often works for high-dimensional data; RBF is the most common non-linear choice.

Why it works.

The rule behind the sequence. “Try linear first” is not just about speed — it is a statement about the bias-variance tradeoff and about what your data’s shape implies. A linear SVM in dimensions can shatter at most points, so its capacity is bounded by the feature count; an RBF SVM has effectively unbounded capacity. When is comparable to or larger than , the linear model already has enough capacity to separate the data, and adding more can only add variance. That is why the linear-versus-RBF decision is well predicted by the ratio rather than by trial and error.

Diagnosing rather than guessing. The learning curve tells you which direction to move. If training and validation error are both high and close together, you are underfitting — a more expressive kernel or a larger will help. If training error is near zero and validation error is much higher, you are overfitting, and the answer is a simpler kernel, smaller , smaller , or more data. Reading this off a curve is faster and more reliable than expanding the grid.

The math, and what it buys you.

A concrete protocol. Standardise the features (fitting the scaler on the training fold only, inside the cross-validation loop, or you leak test statistics into training). Fit a linear SVM across and record cross-validated performance. Then fit RBF over the outer product of that grid with — 25 combinations, on a log scale because these parameters act multiplicatively. Compare the best of each. If RBF beats linear by less than the standard error across folds, take linear: it is faster, it gives interpretable weights, and it has fewer ways to fail on new data. Then refine with a finer grid around the winning region, or use random search, which finds good regions with fewer evaluations when only one of the two parameters actually matters.

Good and bad.

  • Where kernel SVMs win: the small-, high- regime (a few hundred samples, thousands of features, as in some biological assays), where trees struggle and the margin-maximisation prior is a real advantage.
  • The honest caveat. Before running any of this, ask whether an SVM is the right model. On tabular data of moderate size, gradient-boosted trees (XGBoost, LightGBM, CatBoost) typically beat a tuned kernel SVM while needing far less preprocessing — no scaling required, categorical features handled natively, missing values handled natively, and training that is near-linear in rather than quadratic. Saying this unprompted is usually a stronger signal than any amount of grid-search detail.

Follow-up: What if you cannot tell which kernel matches the structure? Use multiple kernel learning, which learns a convex combination of candidate kernels jointly with the classifier, letting the data weight them. In practice it is rarely worth the complexity for a small kernel set — cross-validating over the individual kernels usually gets you the same answer more cheaply — but it is the principled response, and it is genuinely useful when your kernels encode different data sources (one for text, one for images, one for a graph) that must be combined.

Why the interviewer asks this. They want a decision procedure grounded in something — the ratio, learning curves — rather than “try them all and see”.

Saying it out loud. “I start with the shape of the data. If features are comparable to or more numerous than samples — text, genomics, anything wide and sparse — linear is almost always right, because the data’s likely separable already and anything more flexible just adds variance. If I’ve got a few dense features and lots of rows, I’d reach for RBF and grid search C and gamma together on a log scale, because they interact. Then I’d look at the learning curve rather than guessing: both errors high and close means underfitting, big train-test gap means overfitting. And honestly, if it’s tabular data of any real size, I’d check gradient-boosted trees first — they usually beat a tuned kernel SVM and need far less preprocessing.”


See 35_kernel_functions/interview_qa.md for even more detailed answers!


NLP Basics

Q38: Explain TF-IDF. How does it work?

In 30 seconds. “TF-IDF weights a term by how often it appears in this document times how rare it is across the collection. The rarity part is a log, and that matters — without it a word appearing once in a million documents would get a million times the weight. The nice side effect: a word in every document gets log of one, which is zero, so stopwords fall out automatically with no stoplist.”

The short version.

  • TF: — how often the word appears in this document.
  • IDF: — how rare the word is across documents. Common words → low IDF; rare words → high IDF.
  • TF-IDF = TF × IDF: high when a word is frequent here and rare elsewhere, so it identifies characteristic words per document.
  • Example: “algorithm” in a Python tutorial gets high TF and high IDF → high TF-IDF. “the” gets high TF but near-zero IDF → low TF-IDF.
  • L2-normalise the vectors afterwards, then compare with cosine similarity.
  • Uses: text classification features, search-engine ranking, information retrieval.

Why it works.

Where the log in IDF comes from. The logarithm is not cosmetic. Raw inverse document frequency is a ratio that grows without bound: a word appearing in 1 document out of a million would get 1,000,000 times the weight of a word appearing in every document, which is far too aggressive — it would let a single typo dominate a document’s representation. Taking the log compresses this into a difference of magnitudes, so a word appearing in 1% of documents gets roughly twice the weight of one appearing in 10%, not ten times. There is also an information-theoretic reading: is exactly the surprisal of seeing the term in a randomly chosen document, so IDF is measuring how informative the term’s presence is, in the sense of Q23.

Why L2 normalisation is applied afterwards. Without it, a long document has larger counts and therefore a longer vector, so it would score higher against any query purely because of length. Normalising each document vector to unit length means cosine similarity — the dot product of two normalised vectors — measures only the angle, that is, the relative composition of terms. This is why cosine similarity, not Euclidean distance, is the standard metric for TF-IDF vectors.

The math, and what it buys you.

Compute it on a small corpus of documents, for a document of 100 words, and the design of the metric becomes visible in one table:

termcount in docTFTF-IDF
“the”70.0710000.000
“model”50.051000.115
“quantisation”30.03100.138

Note what happened to “the”: appearing in every document gives , which zeroes it out entirely — TF-IDF performs stopword removal automatically, without a stopword list. Note also that “quantisation” outscores “model” despite appearing less often, because rarity beat frequency. That interaction is the entire point of the metric.

The smoothing you will see in real implementations. A term in the vocabulary but absent from the corpus would give division by zero, so scikit-learn uses . The trailing matters: it stops a term appearing in every document from being zeroed out completely, on the grounds that it may still be worth a little. There are also variants for TF — sublinear scaling is common, on the reasoning that a word appearing 20 times is not 20 times more relevant than one appearing once. BM25, the standard ranking function in search engines, extends the same idea with a saturating TF term and explicit document-length normalisation, and it consistently beats plain TF-IDF for retrieval.

Good and bad.

  • Good: exact on rare terms, fast, no training or GPU, fully interpretable — you can point at which terms produced a score.
  • Bad: no notion of synonymy or paraphrase; bag-of-words, so word order is lost; vocabulary-sized sparse vectors.

Follow-up: Why would you still use TF-IDF now that embeddings exist? Because it is exact on rare terms, and that is the failure mode of dense retrieval. Embedding models compress meaning into a few hundred dimensions, which works well for paraphrase and topical similarity but blurs exact tokens — a product code, an error string, a surname, a rare acronym. TF-IDF and BM25 match those exactly by construction. It is also fast, needs no training or GPU, and is fully interpretable: you can point at which terms produced a score. Modern retrieval systems therefore run hybrid search, combining BM25 and dense scores (often via reciprocal rank fusion), because the two fail on complementary queries.

Why the interviewer asks this. It is a cheap probe of whether you can explain a weighting scheme’s design choices — especially the log — rather than reciting the formula.

Saying it out loud. “TF-IDF weights a term by how often it shows up in this document, times how rare it is across the collection. The rarity part is a log, and that matters — without it a word appearing once in a million documents would get a million times the weight and completely dominate. With the log, one appearing in one percent of docs gets about twice the weight of one in ten percent. The nice side effect is that a word in every document gets log of one, which is zero, so stopwords fall out automatically without a stoplist. And you normalise the vectors so that long documents don’t score higher just for being long.”


Q39: What are n-grams? Explain n-gram language models.

In 30 seconds. “An n-gram is a contiguous run of n words, and an n-gram language model factorises sentence probability with a Markov assumption — truncate the history to the last n-1 words and estimate each conditional by counting. The trouble is the counting doesn’t scale: with a 50,000-word vocabulary there are possible trigrams, so almost all are zero, and one zero kills the whole sentence probability.”

The short version.

  • Unigram: single words. Bigram: pairs. Trigram: triplets.
  • Bigram model: , with .
  • Higher n = more context, better predictions — but exponentially more parameters and worse sparsity.
  • Uses: language modelling, text generation, spell checking — and BLEU/ROUGE are built on them.
P(w₁, w₂, ..., wₙ) ≈ P(w₁) × P(w₂|w₁) × P(w₃|w₂) × ... × P(wₙ|wₙ₋₁)

Where:
P(wᵢ|wᵢ₋₁) = count(wᵢ₋₁, wᵢ) / count(wᵢ₋₁)

Why it works.

The Markov assumption is what is actually being assumed. The exact chain rule for a sentence is — every word conditioned on the entire history, with no approximation. That is unestimable, because almost every long history occurs at most once in any corpus. An n-gram model makes the -order Markov assumption: that the history can be truncated,

so a bigram model claims one word of context is enough. The approximate sign in the formula above is doing all the work, and everything that is wrong with n-gram models follows from it: “The man who was standing by the door that I mentioned earlier ___” needs a subject-verb agreement decision that is 12 words away, and no fixed you can estimate will reach it.

How this connects to what replaced it. A neural language model attacks exactly this problem by representing each word as a dense vector, so “dog” and “cat” occupy nearby points and evidence about one informs the other — counts cannot share statistical strength between words, embeddings can. A transformer goes further and drops the fixed window entirely: self-attention conditions on the whole context, so the Markov assumption disappears rather than being loosened. Seeing n-gram models as “the chain rule plus a truncation you cannot afford” makes it obvious what each successor removed.

The math, and what it buys you.

Why you cannot just raise — the sparsity arithmetic. With a vocabulary of , the parameter count is : 2.5 billion bigrams, trigrams, 4-grams. A corpus of a billion tokens contains at most a billion distinct 4-grams, so the overwhelming majority of the table is zero — not “rare”, but never observed. And each zero is fatal under the product formula, since one zero factor sends the entire sentence probability to zero. This is the sparsity wall, and it is why smoothing (Q40) is not a refinement but a requirement, and why practical n-gram models topped out around with heavy smoothing.

A worked count. Corpus: “the cat sat”, “the cat ran”, “the dog sat”. Then , , , so and . Also , , giving . The probability of “the cat sat” under the bigram model is . Notice that “the dog ran” — a perfectly good sentence — gets probability zero, because that bigram was never observed. These MLE estimates are just normalised counts, which is what makes n-gram models trivially fast to train and impossible to generalise.

Good and bad.

  • Good: trivially fast to train (just counting), no GPU, exactly interpretable, and still the mechanism inside BLEU and ROUGE.
  • Bad: the sparsity wall above; no sharing of statistical strength between similar words; and a hard ceiling on context length that makes long-range agreement unreachable.

Follow-up: Are n-grams still used? Yes, in places where their weaknesses do not matter. They are extremely fast, need no GPU, and are exactly interpretable, so they still appear in production spell-checking and autocomplete, in the statistical components of some machine-translation and speech systems, in KenLM for shallow fusion during ASR decoding, and pervasively as features — character n-grams remain a strong, cheap baseline for language identification and authorship attribution. They are also the mechanism inside BLEU and ROUGE (Q42, Q43), so understanding them is not optional even in an all-neural pipeline.

Why the interviewer asks this. It tests whether you can state the Markov assumption precisely and connect its failure to why neural language models exist.

Saying it out loud. “An n-gram is just a contiguous run of n words, and an n-gram language model factorises sentence probability with a Markov assumption — you truncate the history to the last n minus one words and estimate each conditional by counting. The trouble is the counting doesn’t scale: with a fifty-thousand-word vocabulary there are ten to the fourteen possible trigrams, so almost all of them are zero in any corpus, and a single zero kills the whole sentence probability. That’s why you need smoothing. And it fundamentally can’t handle long-range dependencies — agreement across a dozen words is just out of reach. Neural models fixed both: embeddings let similar words share evidence, and attention drops the fixed window entirely.”


Q40: What is Laplace smoothing? Why is it needed?

In 30 seconds. “Smoothing exists because one unseen n-gram gives probability zero, and since you’re multiplying probabilities that zeroes out the whole sentence. Add-k pretends you saw every possible continuation k extra times — numerator gets k, denominator gets k times the vocabulary size, so it still sums to one. The problem is that at realistic vocabulary sizes add-one shoves nearly all your mass onto words you’ve never seen, which wrecks the estimate.”

The short version.

  • Problem: unseen n-grams have ; the product of probabilities becomes 0; the model cannot handle unseen text.
  • Solution: , with the smoothing parameter (often 1) and the vocabulary size.
  • Effect: seen n-grams get slightly lower probability, unseen ones get non-zero probability — mass is redistributed from seen to unseen.
  • Example: trained on “the cat”, “the dog”; test “the bird”. Without smoothing ; with smoothing .
  • Add-one is usually too much. Use , or a better method (backoff, interpolation, Good-Turing, Kneser-Ney).
P(wᵢ|wᵢ₋₁) = (count(wᵢ₋₁, wᵢ) + k) / (count(wᵢ₋₁) + k*V)

Where:
- k: Smoothing parameter (usually 1)
- V: Vocabulary size

Why it works.

Add- smoothing pretends you saw every possible continuation extra times. The numerator gains , and the denominator gains because there are possible next words each receiving phantom counts — that is what keeps the distribution summing to one, and it is the part people forget.

The Bayesian reading. Add- is exactly the posterior mean under a symmetric Dirichlet prior with concentration over the multinomial — add-one is a uniform prior, Laplace’s original “rule of succession”. This is worth saying because it reframes the hyperparameter: is a pseudocount, your prior strength in units of observations, so choosing against a real count of 10 is asserting a prior worth observations against 10 real ones. Stated that way, its excessiveness is obvious.

What better methods do differently. The deeper flaw in add- is that it treats all unseen continuations as equally likely, which is obviously wrong: after “the”, an unseen “aardvark” and an unseen “problem” should not get the same probability. The fixes all bring in information from lower-order models. Backoff falls back to the -gram estimate when the -gram count is zero. Interpolation always mixes orders, , with the s tuned on held-out data. Good-Turing estimates the total mass of unseen events from the count of things seen exactly once, which is an elegant and surprisingly accurate trick. Kneser-Ney, the best of the classical methods, subtracts a fixed discount from every observed count and — its key insight — backs off not to how frequent a word is but to how many distinct contexts it appears in. That is why it correctly gives “Francisco” a low backoff probability despite it being common: it almost only ever follows “San”, so it is a poor guess in a novel context.

The math, and what it buys you.

Completing the toy example shows the redistribution explicitly. The example above assumes a vocabulary of (“cat”, “dog”, “bird”) with , giving . Worth completing the picture: the seen continuations drop from each to . So the three probabilities are , summing to 1 — the unseen event was funded by taking mass from the observed ones.

Why add-one is usually too much — the number that settles it. The mass moved to unseen events is , and is large. With a realistic and a context seen 10 times, add-one gives the observed continuations — the denominator is 5,000 times the actual count, so a continuation observed 5 times out of 10 goes from probability 0.5 to about 0.00012. Essentially all the probability mass has been handed to word types never seen in this context. That is not a small correction; it is a destruction of the estimate. Hence (add- or Lidstone smoothing, with around 0.01-0.1 tuned on held-out data), and hence the fact that better methods exist.

Follow-up: Do modern neural language models need smoothing? Not in this form, because a softmax over the vocabulary assigns strictly positive probability to every token by construction — is never zero — so zero probabilities cannot occur and there is nothing to fix. The analogous concern is overconfidence, and the analogous tool is label smoothing: train against a target of for the correct token and spread over the rest, which prevents the logits from growing without bound and improves calibration. Same instinct — do not let a model assert certainty — implemented on the target side rather than the count side.

Why the interviewer asks this. The formula is easy; knowing why the denominator gains and why add-one is too blunt at realistic vocabulary sizes is the discriminating part.

Saying it out loud. “Smoothing exists because a single unseen n-gram gives you probability zero, and since you’re multiplying probabilities that zeroes out the whole sentence. Add-k pretends you saw every possible continuation k extra times — the numerator gets k and the denominator gets k times the vocabulary size, so it still sums to one. The problem is that with a fifty-thousand-word vocabulary, add-one shoves nearly all your probability mass onto words you’ve never seen in that context, which wrecks the estimate. So you use a much smaller k, or better, something like Kneser-Ney that backs off to lower-order counts — and its clever bit is backing off to how many distinct contexts a word appears in, not how often it appears.”


Q41: Explain the Bayesian interpretation of L1/L2 regularization.

In 30 seconds. “If you do MAP estimation instead of maximum likelihood, you’re minimising negative log-likelihood plus negative log-prior — and that second term is exactly your regulariser. A Gaussian prior gives a sum of squares, so that’s L2; a Laplace prior gives a sum of absolute values, so that’s L1. The regularisation strength is the inverse of the prior’s width, and L1’s sparsity falls out of the kink at zero.”

The short version.

L2 (Ridge)L1 (Lasso)
FrequentistLoss = MSE + Loss = MSE +
BayesianPrior Prior
Prior shapeSmooth bell curveSharp peak (kink) at 0, fatter tails
Belief encodedAll coefficients smallish, none exactly zeroMost coefficients ~0, a few genuinely large
EffectShrinks all parameters toward 0, no sparsityShrinks parameters to exactly 0 — feature selection
Use whenPreventing overfitting, all features relevantFeature selection, many irrelevant features

Why it matters: it tells you which regulariser to pick, explains why L1 creates sparsity, and gives a meaning — it is the inverse prior variance.

Why it works.

Why the shapes produce different behaviour, in prior terms. Both densities peak at zero, but the Laplace density has a kink there — it is not differentiable at 0, and it is much more sharply peaked, with correspondingly fatter tails. Compare the two at equal variance: the Laplace prior puts more mass very near zero and more mass far out, and less in the middle range. That is precisely the belief “most coefficients are essentially zero, but a few are genuinely large”, which is a sparsity prior. The Gaussian’s smooth quadratic peak says “all coefficients are smallish and none are exactly zero”, so its MAP solution has no reason to land on an axis. The kink at zero is what makes the MAP estimate stick there, which is the same fact as the constant-gradient argument in Q20, seen from the probabilistic side.

The math, and what it buys you.

The derivation takes four lines and makes the correspondence exact rather than analogical. Maximum a posteriori (MAP) estimation maximises the posterior . Take the negative log to turn it into a minimisation:

The first term is the ordinary loss — for Gaussian noise it is the sum of squared errors. The second term is the penalty, and it is entirely determined by the prior. Substitute a zero-mean Gaussian prior and — that is L2, with . Substitute a Laplace prior and — that is L1, with . So the regulariser is the negative log prior, and the regularisation strength is the inverse prior width.

That relation, , is the useful takeaway: a strong penalty is a narrow, confident prior that the weights are near zero; a weak penalty is a wide, vague one. Setting is a flat prior, which recovers maximum likelihood — regularisation and priors are the same knob.

Good and bad.

  • What the framing buys you: it turns from an arbitrary dial into a statement of belief, and it tells you what a new penalty term would mean before you try it.
  • One caveat that shows depth. The MAP estimate under a Laplace prior is sparse, but the posterior mean under the same prior is not — the posterior assigns zero probability to any weight being exactly zero, since it is a continuous distribution. So “L1 gives sparsity” is a property of the MAP point estimate specifically, not of Bayesian inference with a Laplace prior. Genuinely Bayesian sparsity requires a spike-and-slab prior (a point mass at zero mixed with a broad distribution) or a continuous approximation to it such as the horseshoe.

Follow-up: What is the Bayesian view of early stopping and dropout? Early stopping is an implicit regulariser: starting from small initialisation and halting before convergence keeps the weights near the origin, which for a linear model can be shown to be approximately equivalent to L2 with a that decreases as training continues. Dropout has a stronger version of the correspondence — Gal and Ghahramani showed that a network trained with dropout is performing approximate variational inference in a deep Gaussian process, which is exactly why keeping dropout on at test time (Q21’s follow-up) gives a usable posterior sample. The general pattern is that most regularisers can be read as priors, and reading them that way tells you what belief you are actually encoding.

Why the interviewer asks this. It tests whether you can move between the optimisation and probabilistic framings of the same object — a good proxy for having real statistical grounding rather than recipe knowledge.

Saying it out loud. “If you do MAP estimation instead of maximum likelihood, you’re minimising negative log-likelihood plus negative log-prior — and that second term is exactly your regulariser. Put a Gaussian prior on the weights and the negative log is a sum of squares, so that’s L2. Put a Laplace prior on and you get a sum of absolute values, so that’s L1. The regularisation strength is the inverse of the prior’s width, so a big lambda is just a narrow, confident prior that the weights are near zero. And the reason L1 gives sparsity falls out of the shape — the Laplace density has a kink at zero rather than a smooth peak, so the MAP estimate actually sticks there.”


See 36_nlp_basics/regularization_priors.md for comprehensive explanation!


Q42: Explain BLEU score. How is it calculated?

In 30 seconds. “BLEU is n-gram precision from one to four, combined with a geometric mean, times a brevity penalty. Two details matter: clipping caps how much credit a repeated word earns at its count in the reference, so ‘the the the the’ can’t score perfectly; and the geometric mean means if any order has zero matches the whole score is zero — which is why sentence-level BLEU is usually zero and you’re meant to compute it over a corpus.”

The short version.

  • N-gram precision for : = matching n-grams / total n-grams in the candidate, clipped at the reference count.
  • Brevity penalty: if the candidate is longer than the reference, else .
  • Formula: , with usually .
  • Range 0 to 1: 1.0 perfect, 0.5-0.7 good, below 0.3 poor.
BLEU = BP * exp(Σ w_n * log(p_n))

Where:
- w_n: Weights (usually [0.25, 0.25, 0.25, 0.25])
- p_n: n-gram precisions
  • Limitations: no notion of meaning, poor with synonyms, and precision inherently favours brevity (BP is only an approximate correction).

Why it works.

Clipping is the mechanism worth explaining. Without it, a candidate of just “the the the the” would score against a reference containing “the” twice — perfect precision from pure repetition. Clipping caps the credit at how many times the word actually appears in the reference, so that candidate scores . This is what stops the metric from being trivially gamed by repeating high-frequency words.

Why BLEU uses precision rather than recall. Recall is ill-defined with multiple valid references — a translation cannot be expected to contain all n-grams of all references. Precision plus a brevity penalty is the workaround: precision stops you adding junk, and BP stops you from gaming precision by saying almost nothing. So the note that BLEU “favours shorter translations even with BP” is best stated as: precision alone rewards brevity, and BP is the correction, which is approximately but not perfectly calibrated.

The math, and what it buys you.

Walking a full calculation is where the metric stops being a formula. Reference: “the cat is on the mat”. Candidate: “the cat the cat on the mat” (7 tokens).

Unigram precision with clipping. Candidate unigram counts: the3, cat2, on1, mat1. Reference counts: the2, cat1, is1, on1, mat1. Clipping caps each candidate count at the reference count, so “the” contributes and “cat” contributes , plus 1 each for “on” and “mat”. Total matched , out of 7 candidate unigrams, so .

Bigram precision. Candidate bigrams: “the cat”, “cat the”, “the cat”, “cat on”, “on the”, “the mat” (6 total). Reference bigrams: “the cat”, “cat is”, “is on”, “on the”, “the mat”. Matches with clipping: “the cat” appears twice in the candidate but once in the reference, so it contributes 1; “on the” contributes 1; “the mat” contributes 1. So . Note how the higher-order precisions are what actually penalise the scrambled word order — unigram precision barely noticed.

Brevity penalty. Candidate length 7, reference length 6. Since , ; no penalty. If the candidate had been just “the cat” (), , cutting the score by a factor of seven.

Combining. BLEU uses the geometric mean of the , and that choice matters: a geometric mean is zero if any single term is zero. So a candidate with no matching 4-grams scores exactly zero regardless of how good its unigram precision is. This is why BLEU on a single short sentence is often 0 and why the metric is designed to be computed at the corpus level, aggregating the numerators and denominators over all sentences before dividing. Sentence-level BLEU needs smoothing (adding a small count to zero n-gram matches) to be usable at all.

Good and bad.

  • Good: cheap, deterministic, language-agnostic, and universally understood, which is why it persists as a baseline.
  • Bad: surface n-grams only, so no credit for synonyms or paraphrase; zero-valued on short outputs; and not comparable across papers unless tokenisation, casing and reference count match.

Follow-up: What are the practical gotchas when reporting BLEU? That BLEU numbers are not comparable across papers unless the tokenisation, casing, and number of references match — a difference in tokenisation alone can move the score by several points. This is exactly why SacreBLEU exists: it takes detokenised text and applies a fixed internal tokenisation, and it emits a version signature so a score can be reproduced. On modern systems, learned metrics such as COMET and BLEURT correlate substantially better with human judgement because they compare meaning via pretrained representations rather than surface n-grams, and chrF (character n-gram F-score) is a better lexical metric for morphologically rich languages. BLEU persists mainly as a cheap, deterministic, universally understood baseline.

Why the interviewer asks this. Clipping and the geometric mean are the two details that separate someone who has implemented BLEU from someone who has called it.

Saying it out loud. “BLEU is n-gram precision from one to four, combined with a geometric mean, times a brevity penalty. The two details that matter are clipping and the geometric mean. Clipping caps how much credit a repeated word can earn at its count in the reference — otherwise outputting ‘the the the the’ would score perfect unigram precision. And the geometric mean means if any order has zero matches, the whole score is zero, which is why sentence-level BLEU is usually zero and you’re meant to compute it over a whole corpus. It’s precision-based because recall doesn’t make sense with multiple valid references, and the brevity penalty is there to stop you gaming precision by saying almost nothing.”


Q43: Explain ROUGE score. What are ROUGE-1, ROUGE-2, ROUGE-L?

In 30 seconds. “ROUGE measures overlap between a generated summary and a reference: ROUGE-1 is unigrams, ROUGE-2 bigrams, and ROUGE-L uses the longest common subsequence, so it rewards keeping the right order without demanding adjacency. It’s recall-oriented because summarisation is about coverage, where BLEU is precision-oriented because translation is about correctness. The big weakness is that it’s pure surface overlap, so it punishes good paraphrasing.”

The short version.

What it measuresFormulaFocus
ROUGE-1Unigram overlapoverlapping words / words in referenceContent coverage
ROUGE-2Bigram overlapoverlapping bigrams / bigrams in referenceWord order and phrases
ROUGE-LLongest common subsequence (need not be contiguous)LCS(cand, ref) / length(ref)Sentence structure and order
  • Returns precision, recall and F1.
  • Uses: the primary metric for summarisation; secondary for general text generation.
  • Versus BLEU: BLEU is precision-oriented (translation); ROUGE is recall-oriented (summarisation); ROUGE-L is the better choice for order-sensitive tasks.

Why it works.

Why recall-oriented, and what it costs. In summarisation the question is “did the summary cover the important content of the source”, which is a recall question; in translation the question is “is what you produced correct”, which is precision. Hence the split with BLEU. But pure recall is trivially gamed by producing a long summary, which is why every modern report uses the F1 variants and why summarisation evaluations must control for length — a system that writes longer summaries will show higher ROUGE recall while being no better.

The math, and what it buys you.

Reference: “the cat sat on the mat”. Candidate: “the cat was on the mat”.

ROUGE-1. Overlapping unigrams: the2, cat, on, mat . Reference has 6 unigrams, candidate has 6, so recall , precision , F1 .

ROUGE-2. Reference bigrams: “the cat”, “cat sat”, “sat on”, “on the”, “the mat” (5). Candidate bigrams: “the cat”, “cat was”, “was on”, “on the”, “the mat” (5). Overlap: “the cat”, “on the”, “the mat” . Recall , precision . Notice the single substituted word destroyed two bigrams while costing only one unigram — bigram scores fall roughly twice as fast, which is why ROUGE-2 is always much lower than ROUGE-1 and why the two numbers are not comparable to each other.

ROUGE-L. The longest common subsequence is “the cat on the mat”, length 5 — subsequences need not be contiguous, which is the whole point: ROUGE-L rewards preserved order without demanding adjacency, so an inserted or substituted word costs you that word but not the surrounding structure. Recall , precision , and the reported figure is the F-measure. A detail worth knowing: ROUGE-L as originally defined uses an F-measure weighted by strongly favouring recall, and there are two variants — sentence-level LCS, and ROUGE-Lsum, which computes LCS per sentence and aggregates. Summarisation papers usually report ROUGE-Lsum, and the two differ enough that mixing them up invalidates a comparison.

Good and bad.

  • Good: cheap, standard, comparable with a decade of prior work, and ROUGE-L captures ordering without demanding exact phrase matches.
  • Bad — the limitation that matters most. ROUGE counts surface n-gram overlap, so it cannot see paraphrase. “The film was excellent” and “the movie was superb” share almost no unigrams and would score near zero against each other despite being the same statement. That is a serious problem now that abstractive summarisers genuinely paraphrase — ROUGE systematically undervalues good abstractive output and overvalues extractive output that copies phrases verbatim. Two consequences follow: a purely extractive baseline (say, “take the first three sentences”, the notorious Lead-3 baseline on news) is hard to beat on ROUGE while being obviously worse to read, and reported ROUGE gains often do not survive human evaluation.

Follow-up: What would you use instead? BERTScore matches tokens via contextual embeddings and cosine similarity, so paraphrase is credited; it correlates far better with human judgement while staying reference-based. For faithfulness specifically — whether the summary states anything the source does not — n-gram overlap is the wrong tool entirely, and the standard approaches are entailment-based (does the source entail each summary sentence) or QA-based (generate questions from the summary and check the source answers them the same way). Increasingly the practical choice is LLM-as-judge with a rubric, which correlates well but introduces its own biases toward length and toward the judge model’s own style. The honest answer in an interview is that you report ROUGE for comparability with prior work and something else for the decision you are actually making.

Why the interviewer asks this. Usually to reach the limitation: knowing that ROUGE cannot see paraphrase, and that this biases evaluation toward extractive systems, is the substantive part.

Saying it out loud. “ROUGE measures overlap between a generated summary and a reference. ROUGE-1 is unigrams, ROUGE-2 is bigrams — and bigrams drop much faster, because one substituted word breaks two bigrams but only one unigram. ROUGE-L uses the longest common subsequence, which doesn’t have to be contiguous, so it rewards keeping the right order without demanding exact adjacency. It’s recall-oriented because in summarisation the question is coverage, whereas BLEU is precision-oriented because in translation the question is correctness. The big weakness is that it’s pure surface overlap — ‘the film was excellent’ and ‘the movie was superb’ score near zero against each other — so it quietly punishes good paraphrasing and rewards copying.”


Q44: How do you handle large database schemas in NL2Code?

In 30 seconds. “With a big schema the hard part isn’t writing SQL, it’s working out which four tables out of a thousand the question is about — schema linking is where most of the errors live. So it’s a retrieval problem first: verbalise each table into a description, index it, retrieve with hybrid lexical plus embedding search, then expand along foreign keys. Tune that stage for recall, not precision, because an extra table is cheap and a missing one is fatal.”

The short version.

Problem: large schemas (thousands of tables and columns) do not fit in the context window.

  1. Schema pruning. Score tables/columns for relevance (TF-IDF, embedding similarity, keyword matching), take top-K, and prune hierarchically — table level first, then column level.
  2. Schema encoding. Hierarchical encoding, graph neural networks over the schema graph, or encoding the schema separately and combining later.
  3. Two-stage approach. Stage 1: schema selection. Stage 2: code generation given the selected schema.
  4. Retrieval-augmented. Retrieve the relevant schema, add it to the context dynamically, and iterate to refine the selection.
Query → Schema Pruning → Schema Encoding → Code Generation → Code

Example: query “Find customers who bought products in 2023” → pruned schema of customers, orders, products plus relevant columns → SQL with the right JOINs.

Best practices: index schemas for fast retrieval, add schema descriptions, handle schema versioning, validate generated code.

Why it works.

Why this is a retrieval problem before it is a generation problem. With a thousand tables, the model’s difficulty is not writing SQL, it is deciding which four tables the question is about. Published error analyses on cross-domain text-to-SQL consistently find schema linking — connecting phrases in the question to the right columns and tables — is the dominant error source, ahead of SQL syntax or join logic. So most of the engineering effort belongs in the selection stage, and the accuracy ceiling of the whole system is set by the recall of that stage: if pruning drops a needed table, no amount of generation quality recovers it. That asymmetry dictates the tuning target — optimise the pruning stage for recall, not precision, and let the generator discard the extras. Retrieving 30 candidate tables of which 4 are needed is fine; retrieving 5 of which one needed table is missing is fatal.

What “relevance scoring” actually has to handle. Naive embedding similarity between the question and a column name fails on the cases that matter, because schema names are rarely natural language: cust_dob, t_ord_hdr, flg_actv. Three things help concretely. First, verbalise the schema — turn each table into a sentence (“Table orders: one row per customer order, with columns order_id, customer_id, order_date, total_amount”) using column comments and any data dictionary, and embed that rather than the raw identifier. Second, include sample values, because a question mentioning “California” links to a column only if you know that column contains state names; indexing distinct values of low-cardinality string columns is one of the highest-leverage additions available. Third, combine lexical and semantic retrieval, since exact matches on identifier fragments are precisely what dense embeddings blur.

Foreign keys change the retrieval problem. Tables are not independent documents — selecting orders and products without the order_items join table produces SQL that cannot be written. So after scoring, expand the selected set along foreign-key edges to include any table on a join path between selected tables. This is graph closure, not ranking, and it is a common omission: the join table often has no lexical or semantic overlap with the question at all and will never be retrieved on its own merits.

Good and bad.

  • The two-stage pipeline — good: it keeps the generation prompt small and cheap, and it gives you a separately measurable stage to fix.
  • Bad, and the mitigations. The failure mode of a hard two-stage pipeline is that stage-1 errors are unrecoverable. Two mitigations: keep a generous candidate set as described above, and add a validation-and-repair loop — run EXPLAIN or execute the generated query against the database, and feed any error back to the model with the message and possibly extra schema. A large share of errors are mechanically detectable (unknown column, ambiguous reference, type mismatch) and a single repair round fixes many of them. Execution-guided decoding, where you filter candidate queries by whether they run and return a non-empty result, is the stronger version of the same idea.

Follow-up: How do you evaluate an NL2SQL system? Not by string match against a reference query — there are many correct SQL statements for one question, differing in join order, aliasing, or subquery versus CTE. The standard metric is execution accuracy: run both the predicted and reference query against a real database and compare result sets. For that to be meaningful the test data must exercise the distinctions you care about, since an underpopulated table can make a wrong query return the same rows as a right one — which is what test-suite accuracy addresses, by running against several databases chosen to distinguish semantically different queries. Alongside that, track schema-linking recall separately, because it tells you which stage to fix.

Why the interviewer asks this. It is a systems-design question wearing an NLP costume; they want to see you decompose it into retrieval, generation, and validation with a clear view of where the errors concentrate.

Saying it out loud. “The core insight is that with a big schema the hard part isn’t writing SQL, it’s working out which four tables out of a thousand the question is even about — schema linking is where most of the errors are. So it’s really a retrieval problem first. I’d verbalise each table into a description using comments and sample values, index that, and retrieve with a hybrid of lexical and embedding search. Then I’d expand along foreign keys, because the join table you need often has nothing in common with the question. I’d tune that stage for recall rather than precision, since an extra table is cheap and a missing one is fatal. And I’d close the loop by executing the query and feeding errors back for a repair round.”


Q45: What are the standard procedures for different NLP tasks?

In 30 seconds. “They’re all the same three steps — represent the text, predict some structure, score it against a reference — and what’s changed over a decade is only how much of that is learned rather than engineered. So the real question is which tool fits: prompt a general model when you have no labels and need it working now; fine-tune a small encoder when the task is fixed and high-volume. And whatever it is, build a dumb baseline first.”

The short version.

TaskPipelineMetrics
Text classificationPreprocess → features (TF-IDF/embeddings) → model → evaluateAccuracy, F1
NERBIO tagging → embeddings → sequence labelling (CRF/BiLSTM) → extractF1 per entity type (span-level)
Question answeringEncode question + context → attention → extract answer spanEM, F1
Machine translationParallel corpus → tokenisation → seq2seq/transformer → beam searchBLEU, METEOR
SummarisationExtractive: rank and select sentences. Abstractive: encode → generateROUGE-1/2/L
NL2CodeQuery → schema pruning → schema encoding → code generationCodeBLEU, execution accuracy
Text → Preprocessing → Feature Extraction → Model → Output → Evaluation

Key points: start with simple baselines; use pre-trained models where possible; evaluate with task-specific metrics; handle domain-specific challenges.

Why it works.

The unifying observation. Every pipeline in the table above is the same three moves — represent the text, predict a structure, score against a reference — and what changed over the last decade is only how much of it is learned. The classical era engineered the representation (TF-IDF, hand-built features) and used a small task-specific model. The pretrain-then-finetune era learned the representation once and attached a small task head. The current era often replaces the whole middle with a prompt. Being able to say which era a given approach belongs to, and when to pick each, is more useful than the list itself.

When to pick which, concretely. Prompt a general model when you have no labelled data, when the task is fluid or one-off, or when you need it working this week; expect the highest per-item cost and latency. Fine-tune a small encoder (a BERT-family model, 100M-400M parameters) when the task is fixed and high-volume: with a few thousand labels it will typically match or beat a large prompted model on a narrow classification or tagging task, at a thousandth the inference cost and single-digit milliseconds of latency. Fine-tune a generative model with LoRA when the output is free-form but the style or format is specific. The economics matter more than the accuracy in most production decisions, and interviewers notice when a candidate raises them unprompted.

Good and bad.

  • Two things the list omits that dominate real projects. First, the label set is the hard part. For NER the difficulty is almost never the model — it is deciding whether a product name inside a company name is one entity or two, and getting annotators to apply that consistently; inter-annotator agreement below about 0.8 Cohen’s kappa means the ceiling on your model is already set by the noise. Second, evaluation design outranks model choice. A random train-test split leaks when documents come from the same source or share near-duplicates, which is endemic in scraped corpora, and it produces the classic result of a model that scores 0.95 offline and fails on deployment. Splitting by document, by time, or by source is the fix, and choosing the split is a modelling decision, not a bookkeeping one.
  • Metric selection, since the table gives metrics without caveats. Accuracy is misleading under class imbalance — 99% accuracy on a 1%-positive problem is achieved by predicting “no” — so use per-class F1, and be explicit about macro (unweighted class average, which surfaces poor performance on rare classes) versus micro (which is dominated by the frequent ones). NER F1 should be computed on whole spans, not tokens, because getting three of four tokens of an entity right is not three-quarters correct. And for any generation task, remember Q42 and Q43: the automatic metric is a proxy, and if the decision matters, a small human evaluation on a couple of hundred examples will tell you more than a decimal point of ROUGE.

Follow-up: What is the first thing you do on a new NLP task? Build the dumbest possible end-to-end baseline and get it evaluated — majority class, keyword rules, or TF-IDF with logistic regression — before touching a transformer. It takes under an hour and it does three things: it establishes the score anyone must beat, it forces the evaluation harness to exist early, and it frequently exposes that the task is nearly solvable by a keyword, or that the labels are too noisy to learn from at all. Both discoveries are much cheaper to make on day one than after a week of fine-tuning.

Why the interviewer asks this. Breadth check — they want to know whether you have a mental map of the field and can choose an approach for a task you have not seen before.

Saying it out loud. “They’re all the same three steps really — represent the text, predict some structure, score it against a reference — and what’s changed is how much of that is learned rather than engineered. So the question I actually ask is which tool fits: if I’ve got no labels and need something working now, I prompt a general model. If it’s a fixed high-volume classification task, I’d fine-tune a small encoder, because with a few thousand labels it’ll match a big model at a fraction of the cost and latency. And whatever the task, I’d build a dumb baseline first — TF-IDF and logistic regression — because it forces the evaluation harness to exist and it quite often reveals that the labels are too noisy to learn from anyway.”


See 36_nlp_basics/nlp_tasks_and_solutions.md for detailed procedures!


MLE and MAP Estimation

Q46: Derive MLE for a coin flip (Bernoulli distribution).

In 30 seconds. “I write down the probability of exactly the data I saw as a function of theta, take the log because it turns the product into a sum without moving the maximum, differentiate, set to zero — and it collapses to theta equals k over n, the observed fraction of heads. I’d also check the second derivative is negative, and flag that three heads out of three gives theta equals one, which is where a prior starts earning its keep.”

The short version.

  • Setup: flips, heads, .
  • Likelihood: .
  • Log-likelihood: .
  • Derivative: ; set to zero → .
  • Result: — simply the observed proportion of heads.
  • Watch the edge cases: or puts the maximum on the boundary, and the derivative argument does not apply there.

Why it works.

The phrase maximum likelihood names the recipe exactly: write down the probability of the data you actually saw as a function of the unknown parameter, then pick the parameter value that makes that number as large as possible. Nothing else is going on.

The math, and what it buys you.

Here is every step with a word on why the move is allowed.

Step 0 — the model. Each flip is a Bernoulli random variable: it is (heads) with probability and (tails) with probability . Bernoulli just means “a single yes/no trial with a fixed success probability.” We assume the flips are i.i.d. — independent and identically distributed, meaning no flip influences another and every flip uses the same . That assumption is what lets us multiply the per-flip probabilities in the next step.

Step 1 — the likelihood. For one flip with outcome , a compact way to write the probability is ; plug in and you get , plug in and you get . Independence lets us multiply across flips:

Notice the data enters only through and . In statistical language is a sufficient statistic — the order of the flips carries no information about .

Step 2 — take logs. is strictly increasing, so whatever maximizes also maximizes . That is the entire justification, and it is why the move is legal rather than merely convenient:

Step 3 — differentiate. Using and the chain rule on , whose inner derivative is :

Step 4 — set to zero and solve. Setting and cross-multiplying by , which is strictly positive for so it cannot introduce or destroy a root:

Step 5 — check it is a maximum, not a minimum. A stationary point is only a maximum if the function curves downward there. The second derivative is

for every in whenever , so is strictly concave and the stationary point is the unique global maximum. This step is the one candidates skip and interviewers notice.

Worked numbers. Ten flips, three heads: . Three flips, three heads: , i.e. the model now claims the coin never lands tails. That confident nonsense from three data points is the standard motivation for MAP.

Good and bad.

  • Good: a closed form, no hyperparameters, and it is the intuitive answer — the observed proportion.
  • Bad — the edge cases. If the log-likelihood is , which increases as falls, so the maximum sits at the boundary ; symmetrically gives . The derivative-equals-zero argument does not apply at a boundary. This is exactly the pathology that a prior fixes — see Q51.

Follow-up: What is the variance of this estimator? Since has variance , the estimator has variance — it shrinks like , and it is largest at , which is the intuitive statement that a fair coin is the hardest one to pin down.

Why the interviewer asks this. It is the smallest possible derivation that still has all the moving parts, so it reveals whether you actually manipulate likelihoods or merely recite the answer .

Saying it out loud. I write down the probability of exactly the data I saw as a function of theta — that’s theta to the k times one-minus-theta to the n-minus-k. Then I take the log, because log turns that product into a sum and doesn’t move the maximum, since log is increasing. Differentiate, set it to zero, and it collapses to theta equals k over n — just the observed fraction of heads. I’d also check the second derivative is negative so I know it’s a max, and flag that if I saw three heads out of three, MLE says the coin never lands tails, which is where a prior starts earning its keep.


Q47: Derive MLE for linear regression.

In 30 seconds. “Assume Gaussian noise with constant variance, and the log-likelihood is just minus the sum of squared residuals over two sigma squared, plus constants that don’t move the argmax. So maximising likelihood is literally minimising squared error — least squares isn’t a stylistic choice, it falls out of the Gaussian assumption. Differentiate, set to zero, and you get the normal equation, valid as long as X has full column rank.”

The short version.

  • Setup: , .
  • Likelihood: .
  • Log-likelihood: .
  • So: .
  • Derivative: — the normal equation.
  • Result: — ordinary least squares.
  • Key insight: MLE for linear regression with Gaussian noise is OLS.

Why it works.

What the model actually claims. Writing with says three separate things: the mean of given is linear in ; the noise around that mean is Gaussian; and the noise is homoscedastic and uncorrelated — same variance at every point, and no correlation between rows. That last part is what makes the joint density a product over rows.

Geometrically, the normal equation says the residual vector is orthogonal to every column of : the fitted values are the orthogonal projection of onto the column space of . That is the picture worth carrying into the interview.

The math, and what it buys you.

Step 1 — the likelihood. The density of one observation is the Gaussian density evaluated at the residual:

Multiplying over (independence again) and collecting the exponents:

Step 2 — log, and drop constants. Taking logs turns the exponential into the thing in the exponent:

The first term does not contain , so it cannot change where the maximum in sits; drop it. The remaining factor is a positive constant, and scaling an objective by a positive constant does not move its argmax. So

That single line is the whole “Gaussian noise implies least squares” result. Least squares is not a taste in loss functions; it is the maximum-likelihood consequence of assuming Gaussian errors.

Step 3 — expand before differentiating. Write the squared norm as an inner product and expand:

using because a scalar equals its own transpose.

Step 4 — differentiate term by term. The two vector-calculus identities you need are and when is symmetric — and is symmetric by construction. So

Step 5 — set to zero. is the normal equation. Solving,

A note on . It dropped out of the solution but it is still a parameter. Maximizing over gives — the mean squared residual. That is the biased variance estimate; the familiar denominator comes from an unbiasedness correction, not from MLE. Interviewers like this detail because it shows you know MLE is not automatically unbiased.

Good and bad.

  • Good: an exact closed form, a clean geometric reading, and a principled link from a noise assumption to a loss function — which is what lets you invent a loss for a new problem.
  • Bad — when is this valid? The inverse exists only if is invertible, i.e. has full column rank — no exactly collinear features and at least as many rows as columns. The Hessian is , which is positive semi-definite always and positive definite exactly under that full-rank condition, so the stationary point is the unique global minimum. When rank is deficient there are infinitely many optima, and the standard fixes are the pseudo-inverse or a ridge penalty (Q49).

Why the interviewer asks this. They want to see that you can connect a probabilistic assumption to a loss function, because that link is what lets you invent a loss for a new problem instead of guessing.

Saying it out loud. If I assume the noise is Gaussian with constant variance, the likelihood is a product of Gaussians, and its log is just minus the sum of squared residuals over two sigma squared, plus constants. The constants don’t move the argmax, so maximizing likelihood is literally minimizing squared error — least squares isn’t a stylistic choice, it falls out of the Gaussian assumption. Expand the quadratic, take the gradient, set it to zero, and you get the normal equation X-transpose times the residual equals zero, which geometrically says the residual is orthogonal to the column space. Solve and you get X-transpose-X inverse X-transpose y, valid as long as X has full column rank.


Q48: Explain the connection between MLE and MAP.

In 30 seconds. “Start from Bayes’ rule and take logs: the log-posterior is the log-likelihood plus the log-prior, minus the evidence, which is constant in theta so it drops. So MAP is just MLE with an extra term — and that extra term is your regulariser. A Gaussian prior gives L2, which is ridge; a Laplace prior gives L1, which is lasso. And because the likelihood grows with n while the prior stays fixed, priors are leverage on small data and rounding error on large data.”

The short version.

MLEMAP
Objective
Uses a prior?NoYes — the log-prior is the extra term
Equivalent toUnregularised fitRegularised fit: L2 = Gaussian prior, L1 = Laplace prior
Small dataCan be wildly overconfident (3 heads from 3 flips → )Pulled toward the prior mean
Large data MLE; the prior washes out
  • Relationship: MAP = MLE + prior.
  • When they coincide: a uniform prior, or a large enough dataset.
  • When they differ: small datasets, or a strong prior.

Why it works.

Where the prior comes from: Bayes’ rule in one line. The bridge between the two estimators is Bayes’ rule, . Take logs of both sides:

The evidence term does not depend on , so it cannot move the argmax and we discard it. What is left is exactly “MLE plus a log-prior,” which is why MAP looks like a regularized MLE. MAP stands for maximum a posteriori: the mode — the highest point — of the posterior distribution.

The math, and what it buys you.

Why regularizers are log-priors, concretely. A zero-mean Gaussian prior has log-density . Subtracting that from the negative log-likelihood is an L2 penalty — ridge. A Laplace prior has log-density , giving an L1 penalty — lasso. The Laplace density is sharply peaked at zero with heavier tails than a Gaussian, which is the probabilistic reason lasso pushes coefficients exactly to zero while ridge only shrinks them: the L1 penalty has a non-zero-width kink at the origin, so zero is a genuine optimum for a whole range of data, whereas the Gaussian’s smooth parabola has zero gradient at the origin and never pins a coefficient there.

Why “large dataset makes them agree” is more than a slogan. The log-likelihood is a sum of terms and therefore grows linearly in ; the log-prior is a single fixed term that does not grow at all. So the prior’s share of the objective falls like . Under mild regularity conditions this is the Bernstein–von Mises phenomenon: the posterior concentrates on the true parameter and becomes asymptotically Gaussian regardless of which (positive, smooth) prior you started from. The practical reading is that priors are leverage on small data and rounding error on large data.

Good and bad.

  • MLE — good: no prior to justify, closed forms in the standard cases. Bad: overconfident on tiny samples and undefined at boundaries.
  • MAP — good: regularisation with a principled interpretation; stabilises small-data estimates. Bad: it is only half-Bayesian (see the follow-up) and its answer depends on your choice of prior.

Follow-up: Is MAP a Bayesian method? Only half-heartedly. It uses a prior, which is Bayesian, but it reports a single point — the posterior mode — and throws away the uncertainty, which is not. It is also not invariant under reparameterization: the mode of a density changes if you transform the parameter (because the Jacobian reshapes the density), whereas the posterior mean and full posterior transform sensibly. Fully Bayesian inference integrates over the posterior rather than maximizing it.

Why the interviewer asks this. They are checking whether you see regularization as a principled modelling statement rather than a knob you turn until validation loss drops.

Saying it out loud. Start from Bayes’ rule and take logs: the log-posterior is the log-likelihood plus the log-prior, minus the evidence, which is constant in theta so it drops. So MAP is just MLE with an extra term, and that extra term is your regularizer. A Gaussian prior gives you the L2 penalty — that’s ridge — and a Laplace prior gives you L1, which is lasso. And because the likelihood term grows with n while the prior stays fixed, the prior matters a lot on small data and washes out on big data.


Q49: Derive MAP for linear regression with Gaussian prior (Ridge).

In 30 seconds. “Put a zero-mean Gaussian prior on the weights and the MAP estimate is ridge regression — the prior term turns into the L2 penalty, and lambda falls out as the noise variance divided by the prior variance. So ridge isn’t an arbitrary hack: it’s what Bayes tells you to do when you believe the weights are small. The closed form is , and that is also why ridge is numerically safe on collinear data.”

The short version.

OLS / MLERidge / MAP
Objective
Assumption on none (flat prior)
Solution
Invertible when ?NoYes — always
means, noise over prior

Why it works.

The likelihood pulls toward whatever fits the data; the prior pulls it toward zero. MAP is just the tug-of-war between the two, and is the rope length. Noisy data (large ) means the likelihood is less trustworthy, so the prior wins more; a confident prior (small ) means the same thing from the other side. Both push up and shrink the weights harder — which is the sentence that makes ridge stop feeling arbitrary.

The math, and what it buys you.

Here is the whole derivation, one step at a time. The payoff at the end of Step 2 is that the Bayesian objective and the ridge objective are literally the same expression.

Answer:

Setup: y = Xw + ε, prior w ~ N(0, σ²_prior I)

Posterior: log P(w|D) = -||y - Xw||²/(2σ²) - ||w||²/(2σ²_prior)

Derivative: ∂/∂w [log P(w|D)] = -1/σ² × Xᵀ(y - Xw) - 1/σ²_prior × w = 0

Result: ŵ_MAP = (XᵀX + λI)⁻¹Xᵀy where λ = σ²/σ²_prior

Key Insight: MAP with Gaussian prior = Ridge regression (L2 regularization)!

Full walkthrough, one step at a time.

Step 0 — set up both pieces. The likelihood is the same Gaussian-noise model as Q47. The prior says each weight is drawn independently from , written jointly as , where is the prior variance (the answer above calls it ). A small is a confident statement that the weights are near zero; a large is an agnostic one.

Step 1 — write the log-posterior. Dropping every term free of :

The first term comes from the Gaussian likelihood, the second from the Gaussian prior; both are just “quadratic in the exponent.”

Step 2 — flip the sign and rescale. Maximizing that is minimizing its negative, and multiplying by the positive constant leaves the argmin alone:

That is worth pausing on: it is a ratio of variances, noise over prior. Noisy data (large ) or a confident prior (small ) both push up and shrink the weights harder. This is the sentence that makes ridge stop feeling arbitrary.

Step 3 — differentiate. Using the same two identities as Q47 plus :

Step 4 — collect terms in . Distribute and move things across:

Step 5 — why this is strictly better conditioned. is symmetric positive semi-definite, so its eigenvalues are ; adding shifts every eigenvalue up by , making the matrix positive definite and therefore invertible even when is rank deficient or has more columns than rows. The condition number, the ratio of largest to smallest eigenvalue, drops from to . This is the numerical reason ridge is used on collinear data even by people who do not care about Bayes.

What ridge does in the singular-value basis. If is the singular value decomposition, then OLS divides each component by while ridge divides by — equivalently it multiplies the OLS coefficient in direction by the shrinkage factor . Directions with large singular values (well-determined by the data) are barely touched; directions with tiny singular values (where the data says almost nothing) are crushed toward zero. That is exactly the behaviour you would want a prior to have, and seeing it stated this way is usually what separates a good answer from a great one.

Good and bad.

Ridge is good for: collinear or wide () designs, noisy data, anywhere you want a stable well-conditioned solve. It has a closed form and a single interpretable knob.

Ridge is bad at: selecting features — it shrinks everything but zeroes nothing (that is L1/lasso, which is a Laplace prior). It is not scale invariant, so unstandardized features get penalized inconsistently, and it gives you a point estimate only, not the full posterior uncertainty.

Follow-up: Should the intercept be penalized? No. Penalizing it makes the fit depend on where you happen to have put the origin of , so in practice you centre the response and the features and leave the intercept out of the penalty. Features should also be standardized first, since an L2 penalty is not scale invariant — measuring a feature in metres versus kilometres changes how hard it gets shrunk.

Why the interviewer asks this. It tests whether you can carry a derivation through with a second term added, and whether you understand as a signal-to-prior ratio rather than a hyperparameter with no meaning.

Saying it out loud. I add a zero-mean Gaussian prior on the weights, so the log-posterior is the Gaussian log-likelihood minus a term proportional to the squared norm of w. Flip the sign and you’re minimizing squared error plus lambda times w-squared — that’s ridge, and lambda comes out as the noise variance over the prior variance, which is a nice interpretation: noisier data or a tighter prior means more shrinkage. Take the gradient, set to zero, and you get X-transpose-X plus lambda-I, inverse, times X-transpose y. The lambda-I is also why ridge is numerically safe — it bumps every eigenvalue up, so the matrix is invertible even when the features are collinear.


Q50: Why do we use log-likelihood instead of likelihood?

In 30 seconds. “Three reasons, and the first one is the killer: likelihoods are products of thousands of numbers below one, so they underflow to exactly zero in floating point and take every gradient with them. Log turns that product into a sum that’s a perfectly ordinary number. It also turns products into sums so gradients decompose per data point, and it’s free — log is monotonic, so the maximizer doesn’t move.”

The short version.

  • Numerical stability — products of small probabilities underflow; sums of logs don’t.
  • Mathematical convenience — products become sums, so derivatives decompose per data point.
  • Monotonicity — maximizing maximizes ; the argmax is identical.
  • Additivity — log-likelihoods from independent data or independent models just add.
  • Concavity — many likelihood surfaces are non-concave but their logs are concave.

Why it works.

A likelihood over i.i.d. points is a product of numbers in , which shrinks geometrically — it is a number with an exponent that grows linearly in . Floating point stores a bounded exponent, so past a few hundred terms you get zero, and zero is an information-free number: every gradient computed from it is zero and the run is dead. The log undoes the geometric shrinkage exactly, turning “exponent grows linearly” into “value grows linearly,” which is a range computers handle comfortably. Everything else — sums instead of products, concavity, additivity — is a bonus that falls out of the same transformation.

The math, and what it buys you.

Answer:

Reasons:

  1. Numerical stability: Products of small probabilities → underflow, sums are stable
  2. Mathematical convenience: Products become sums, derivatives easier
  3. Monotonicity: Maximizing log L(θ) = maximizing L(θ)
  4. Additive properties: Can combine log-likelihoods easily

Example:

  • Likelihood: 0.1 × 0.1 × 0.1 = 0.001 (very small!)
  • Log-likelihood: log(0.1) + log(0.1) + log(0.1) ≈ -6.91 (manageable)

How bad is the underflow, exactly?

This is not a theoretical worry. A standard 64-bit float underflows to exactly zero below roughly . A language model scoring a 1,000-token document at an average per-token probability of produces a likelihood of — the product is zero in floating point long before you finish, and once it is zero every gradient is zero and the run is dead. The log-likelihood of the same document is , which is a perfectly ordinary number. Any time you see a sum of log-probabilities in a codebase, this is why.

The other three reasons, spelled out.

Products become sums, and sums differentiate independently. The derivative of a product of terms needs the product rule and produces terms each containing all the others; the derivative of a sum is just the sum of derivatives. For an i.i.d. dataset this is the difference between a tractable gradient and an unusable one.

The maximizer is unchanged because is strictly increasing. If then , so the ordering of every pair of candidate parameters is preserved and therefore so is the argmax. Note this is about the location of the maximum: the maximum value obviously changes.

Concavity often appears only after the log. The likelihood surface for many standard models is not concave, but the log-likelihood is — Bernoulli, Gaussian, Poisson and the rest of the exponential family all have concave log-likelihoods in their natural parameters. Concavity is what guarantees a unique optimum and makes gradient ascent reliable, so taking the log can turn a hard optimization into an easy one.

The trick you will actually reach for: log-sum-exp. When you need — which is what a softmax denominator is — computing the exponentials directly overflows for large . The stable form subtracts the maximum first:

Every exponent is now , so every term is in and at least one equals exactly ; nothing overflows and the largest term cannot underflow. This identity is what torch.logsumexp and every framework’s log_softmax implement, and it is why you should call log_softmax rather than log(softmax(x)).

Follow-up: Does taking the log change the estimate’s variance or bias? No — it is the same estimator, since it is the same argmax. What changes is the numerical path you take to find it.

Saying it out loud. Three reasons, really. Numerically, likelihoods are products of thousands of numbers below one, so they underflow to zero in floating point — a thousand tokens at probability 0.05 gives you ten to the minus thirteen hundred, which is just zero to a computer, whereas the log is about minus three thousand and totally fine. Mathematically, log turns products into sums, so gradients decompose per data point. And it’s free, because log is monotonic, so the maximizer doesn’t move. The same idea shows up in log-sum-exp, where you subtract the max before exponentiating so nothing overflows.


Q51: What’s the difference between MLE and MAP in practice?

In 30 seconds. “MLE is the pure-data answer; MAP nudges it toward a prior. The difference only matters when data is scarce — flip a coin three times and get three heads and MLE says tails is literally impossible, which will blow up anything downstream that takes a log. With a Beta(3,3) prior, MAP says 0.71. By a hundred flips the two agree to within a percent, because the likelihood grows with n and the prior doesn’t.”

The short version.

MLEMAP
PhilosophyFrequentistBayesian
Priornone (or uniform)informative
Estimator (Beta prior)
Best whenlarge , no prior knowledgesmall , prior knowledge, need regularization
Failure modeextreme estimates on tiny samplesa bad prior biases you forever
Costessentially identical compute
As converges to MLE

Why it works.

The log-posterior is log-likelihood plus log-prior. The likelihood term grows with ; the prior term does not. So the prior acts like a fixed number of imaginary data points you saw before the experiment started — with four real observations those imaginary points dominate, and with ten thousand they are noise. That single sentence explains every practical difference: MAP is regularization, and regularization only matters when you are data-poor.

The math, and what it buys you.

Answer:

MLE:

  • Frequentist approach
  • No prior (or uniform)
  • Use: Large dataset, no prior knowledge
  • Example: θ̂ = k/n

MAP:

  • Bayesian approach
  • Informative prior
  • Use: Small dataset, have prior knowledge, need regularization
  • Example: θ̂ = (k+α-1)/(n+α+β-2) with Beta prior

Practical:

  • Small data: MLE can be extreme, MAP more reasonable
  • Regularization: MAP provides natural regularization
  • Computation: Similar complexity

A worked Bernoulli/Beta example, end to end.

This is the example to have ready, because it makes every abstract claim above concrete in about thirty seconds of arithmetic.

The Beta distribution is a distribution over a probability — a density on the interval — with two shape parameters and density . It is the conjugate prior for the Bernoulli likelihood, meaning that if the prior is Beta then the posterior is Beta too, so the update is arithmetic rather than integration. Multiply prior by likelihood:

which is by inspection. The update rule is therefore “add your heads to and your tails to ,” which is why and are read as pseudo-counts: a prior behaves like having already seen 2 heads and 2 tails before the experiment started (the “minus one” convention comes from the exponents).

MAP takes the mode of that posterior. Differentiating and setting to zero repeats the Q46 algebra exactly, giving

Note that the mode and the posterior mean are different numbers; MAP is the mode. Now the arithmetic, with a prior — symmetric, centred on , mildly confident:

DataMLE MAP (mode)Posterior mean
3 heads in 100.3000.3570.375
3 heads in 31.0000.7140.667
30 heads in 1000.3000.3080.311

Read the three rows in order and you have the whole lesson. In row one the prior pulls the estimate a little toward . In row two MLE gives the absurd answer that tails is impossible, while MAP gives a cautious — this is the failure mode that makes MAP worth the trouble, and it is the same failure that add-one (Laplace) smoothing fixes in n-gram language models and naive Bayes, which is precisely a MAP estimate under a / Dirichlet prior. In row three the data has ten times the weight and the two estimators have nearly converged, which is the washout from Q48 showing up numerically.

(Verified numerically: with , and .)

Good and bad.

MLE: consistent and asymptotically efficient — under regularity conditions it attains the lowest possible variance for large , and it needs no modelling choices. But it is unstable on small samples and happily assigns probability zero to unseen events.

MAP: trades a little bias for a large variance reduction on small , gives natural regularization, and never returns a degenerate estimate. But it requires you to pick a prior, and a wrong one biases every estimate you ever produce.

One more practical difference worth naming. That trade is the bias–variance tradeoff appearing in an estimation-theory costume, and saying it that way tends to land well.

Follow-up: How do you choose and in practice? Either from domain knowledge expressed as pseudo-counts (“I’d be surprised by a click-through rate outside 1–5%, so pick a Beta with that bulk”), or empirically by fitting the prior to the pooled distribution across many similar items — that is empirical Bayes, and it is how per-item conversion rates get shrunk toward the population rate in production ranking systems.

Why the interviewer asks this. The interesting answer is not the definitions but the small-sample failure of MLE, so this question is really asking whether you have ever been burned by an estimate computed from four data points.

Saying it out loud. Practically, MLE is the pure data answer and MAP nudges it toward a prior. The example I like: flip a coin three times, get three heads, and MLE says the probability of tails is exactly zero — which is nonsense, and it’ll blow up anything downstream that takes a log. Put a Beta-three-three prior on it and MAP says about 0.71, which is a sane answer. With a hundred flips the two agree to within a percent, because the likelihood grows with n and the prior doesn’t. It’s the same idea as add-one smoothing in n-gram models — that’s literally a MAP estimate.


See 37_mle_map_estimation/mle_map_derivations.md for complete derivations! See 37_mle_map_estimation/interview_qa.md for more detailed answers!


Multimodal Models and Embeddings

Q52: Explain CLIP. How does it work?

In 30 seconds. “CLIP trains two encoders — one for images, one for text — so that matching pairs land close together in a shared space. You take a batch of image-caption pairs, build an N-by-N similarity matrix, and do cross-entropy across rows and columns: the diagonal is the true pairs, everything else is a negative. The payoff is zero-shot classification — turn class names into sentences, embed them, pick the nearest — so you can change your label set at inference time without retraining.”

The short version.

  • Two towers: a ViT or ResNet image encoder and a Transformer text encoder, projected into one shared space.
  • Contrastive objective: maximize similarity of the true pairs, minimize the mismatched ones.
  • Scale: 400M web image-text pairs, batch size ~32K.
  • Zero-shot: classification recast as retrieval against embedded prompt sentences.
  • Result: matches supervised baselines on many tasks and is markedly more robust to distribution shift.

Why it works.

The trick is replacing labels with comparisons. The model is never told “this is a cat” — only “this image goes with this caption and not with those other 32,767 captions.” That is a much harder and much richer task, because getting it right requires representing whatever the caption happens to mention. And because the supervision is natural language, the label space is infinite and open: at test time you write new sentences instead of retraining a head.

The analogy: it is a matching exercise, not a multiple-choice test. Two shuffled columns, images on the left and captions on the right, and the model learns to draw the lines.

The math, and what it buys you.

Answer:

CLIP (Contrastive Language-Image Pre-training) learns to align text and images in a shared embedding space.

Architecture:

  • Image Encoder: ViT or ResNet → image embeddings
  • Text Encoder: Transformer → text embeddings
  • Contrastive Learning: Align matching pairs

Training:

  1. Collect 400M text-image pairs from web
  2. Encode images and texts to same space
  3. Contrastive loss: maximize similarity of matching pairs, minimize non-matching
  4. Large batch size (32K) for many negatives

Key Insight: Instead of predicting exact labels, predict which text matches which image.

Zero-Shot Transfer:

  • Create text prompts: “a photo of a cat”
  • Find most similar image
  • Works on new tasks without fine-tuning!

Results:

  • Matches supervised models on many tasks
  • More robust to distribution shifts
  • Strong image-text retrieval

The mechanism, in more detail.

Contrastive learning means training on relative comparisons rather than absolute labels: the model is never told “this is a cat,” only “this image goes with this caption and not with those other 32,767 captions.” Here is how one CLIP training step actually runs.

Take a batch of image–caption pairs. Encode all images into vectors and all captions into , project both into a shared dimension, and L2-normalize every vector so it lies on the unit sphere. Normalization matters: once vectors have unit length, the dot product is the cosine similarity, so the loss cannot be gamed by simply making embeddings longer.

Now form the matrix , where is a temperature that controls how sharp the resulting softmax is. In CLIP is learned rather than fixed — the model is parameterized by and it is clipped to stop it running away. The diagonal entries are the true pairs. Apply a cross-entropy loss across each row (given image , which caption is right?) and across each column (given caption , which image is right?), and average the two. In code that is two cross_entropy(logits, arange(N)) calls, one on the matrix and one on its transpose — it is genuinely about five lines.

Why the batch size is part of the algorithm, not a tuning detail. Every non-diagonal entry is a negative example, so a batch of supplies negatives per anchor for free. The difficulty of the task — and hence how much signal each step carries — scales with . At 32,768 the model must pick the right caption out of tens of thousands, which forces genuinely fine-grained representations. This is also why CLIP-style training needs either very large accelerators or distributed tricks that gather embeddings across devices before computing the loss.

Why zero-shot transfer works at all. Classification is recast as retrieval. You never train a classifier head; you write one sentence per class (“a photo of a {label}”), encode those sentences once, and label an image by nearest caption. The class set is therefore just a list of strings you can change at inference time. Prompt ensembling — averaging the embeddings of several templates per class — reliably adds a point or two, because it averages out the quirks of any single phrasing.

Good and bad.

Good at: open-vocabulary classification and retrieval, robustness to distribution shift, cheap supervision (captions, not taxonomies), and serving as a frozen backbone for downstream vision-language systems.

Bad at: counting, spatial relations (“the cup left of the laptop”), and binding attributes to the right object (“a red cube and a blue sphere” versus the swap) — a well-documented bag-of-words tendency, because a contrastive objective over web captions rarely needs compositional structure to pick the right caption. It also inherits web-scale social biases, and its zero-shot accuracy is sensitive to prompt wording. Contrastive image-text pretraining remains the backbone of most vision-language systems as of 2026, though production systems now typically add a generative captioning or matching objective on top rather than using the contrastive loss alone — this is a fast-moving area; check the current state of the art before quoting specifics.

Follow-up: Why not just train a supervised classifier on 400M images? Because you would first need 400M consistent labels from a fixed taxonomy, which does not exist and would not transfer. Natural-language supervision is both cheaper to collect and richer — the caption “a chest X-ray showing pneumonia” carries structure that the integer class ID 37 does not.

Why the interviewer asks this. CLIP is the cleanest example of turning supervision into a retrieval problem, so the question tests whether you understand contrastive objectives rather than whether you have memorised an architecture diagram.

Saying it out loud. CLIP trains two encoders, one for images and one for text, so that matching pairs land close together in a shared space. You take a batch of image-caption pairs, encode everything, normalize to unit length so dot product is cosine similarity, and build an N-by-N similarity matrix. The diagonal is the true pairs, everything off-diagonal is a negative, and you just do cross-entropy across rows and across columns. Big batches matter because they’re where the negatives come from. The payoff is zero-shot classification — you turn class names into sentences like “a photo of a cat,” embed them, and pick the nearest one, so you can change your label set at inference time without retraining.


Q53: How do you train Word2Vec?

In 30 seconds. “Skip-gram slides a window over the corpus and, for each centre word, tries to predict the words around it. A softmax over a million-word vocabulary per pair is far too expensive, so you reframe it as a binary question with negative sampling: is this centre-context pair real, or did I make it up? Push the dot product up for the real pair, down for five to twenty random fakes. Each update then costs almost nothing, and you get dense embeddings where king minus man plus woman lands near queen.”

The short version.

  • Data: (centre, context) pairs from a sliding window of 5–10 words over billions of tokens.
  • Model: two embedding tables — input (centre) and output (context), with of 100–300.
  • Loss: negative sampling — one binary logistic regression per true pair plus = 5–20 fakes.
  • Tricks that matter: subsample frequent words; draw negatives from ; randomize window size.
  • Output: keep the input table, discard the output table. Hours to days of training.

Why it works.

The objective is a prediction game whose answer nobody cares about — you never use the model to predict context words. What you want is the side effect: to predict the neighbours of a word well, the embedding has to encode the company that word keeps, and words that keep the same company end up in the same place. That is the distributional hypothesis turned into a loss function.

The reason negative sampling is not a cheap approximation but a genuine reformulation: instead of asking a 1M-way question (“which word is the context?”), you ask a handful of yes/no questions (“is this pair real?”). Same signal, a thousandth of the cost.

The math, and what it buys you.

Answer:

Word2Vec Skip-gram:

Architecture:

  • Input: One-hot vector for center word
  • Hidden: Embedding layer (V × d)
  • Output: Softmax over vocabulary (predict context words)

Training:

  1. Create pairs: For each word, create (center, context) pairs from window
  2. Forward pass: Embed center word, predict context words
  3. Loss: -log P(context | center)
  4. Negative sampling: Instead of softmax over all V words, sample k negatives
    • Binary classification: positive (context) vs negative
    • Much faster!

Loss with Negative Sampling:

Loss = -log σ(v_context · v_center) - Σ log σ(-v_neg · v_center)

Training Details:

  • Data: Billions of words
  • Window size: 5-10 words
  • Embedding dim: 100-300
  • Negative samples: 5-20
  • Training: Hours to days

Result:

  • Dense, low-dimensional embeddings
  • Captures semantic relationships
  • “King - Man + Woman ≈ Queen”

Walkthrough of the actual computation.

The one-hot-times-matrix framing in the answer above is how the paper draws it, but it is worth knowing what really happens in code: multiplying a one-hot vector by an embedding matrix is just a row lookup, so the “input layer” is an array index, not a matrix multiply. There are two embedding tables — an input (centre) table and an output (context) table , both of shape (vocabulary size dimension). Most implementations keep at the end and discard , though summing or concatenating the two sometimes helps.

Step 1 — build the training pairs. Slide a window over the corpus. For “the quick brown fox jumps” with window 2 and centre “brown,” you emit (brown, the), (brown, quick), (brown, fox), (brown, jumps). Word2vec actually samples the window size uniformly from to the maximum for each centre word, which has the effect of weighting nearby words more heavily without any extra machinery.

Step 2 — subsample frequent words. Before pair generation, each token is discarded with probability where is the word’s corpus frequency and . This throws away most instances of “the” and “of,” which both speeds training and improves quality, because a co-occurrence with “the” carries almost no information.

Step 3 — the negative sampling loss. The full softmax over a vocabulary of, say, 1M words costs a 1M-way normalization per pair, which is fatal. Negative sampling replaces “which of the 1M words is the context?” with “is this pair real or fake?” — a set of binary logistic regressions. For one true pair and sampled fake contexts :

Reading it in words: push the dot product of the true pair up (first term), push the dot products of random pairs down (second term). Cost per example drops from to with of 5–20.

Step 4 — sample the negatives from a flattened unigram distribution. Negatives are drawn with probability proportional to , not . Raising to the three-quarter power flattens the distribution, so rare words get sampled somewhat more often than their raw frequency would suggest and common words somewhat less. It is an empirical choice that measurably helps.

Step 5 — update. Plain SGD with a linearly decaying learning rate; each example touches only the centre row of and rows of , so updates are extremely sparse and cheap.

Why the analogy trick works, mechanically. “King − Man + Woman ≈ Queen” is not magic. The objective drives toward roughly the pointwise mutual information between the two words, so differences of vectors encode ratios of co-occurrence probabilities. The vector “king minus man” isolates whatever co-occurrence pattern distinguishes royalty-with-male-context from male-context, and adding “woman” re-applies it. Worth mentioning the caveats too: the standard evaluation excludes the three input words from the nearest-neighbour search, and if you do not exclude them the answer is frequently just “king” again. That detail signals you have actually run the code.

Good and bad.

Good: extremely cheap to train, sparse updates, embeddings that capture strong semantic and syntactic regularities, and a vocabulary-sized table you can ship anywhere.

Bad: one vector per word, so no polysemy (“bank” gets one point between the river and the money); no subword information, so out-of-vocabulary words have no vector at all (fastText fixes this); and it learns social biases straight out of the corpus.

Follow-up: Skip-gram or CBOW? CBOW predicts the centre word from the averaged context, is several times faster, and is better on frequent words. Skip-gram predicts each context word from the centre, sees each rare word many times as an anchor, and is better on small corpora and rare words. Skip-gram with negative sampling is the usual default.

Saying it out loud. Skip-gram slides a window over the corpus and, for each centre word, tries to predict the words around it. The catch is that a softmax over a million-word vocabulary per training pair is way too expensive, so instead you use negative sampling — reframe it as a binary question: is this centre-context pair real, or did I make it up? You push the dot product up for the real pair and down for maybe five to twenty random fake ones, and now each update costs almost nothing. There are a couple of tricks that matter in practice: subsampling frequent words like “the,” and drawing negatives from the frequency distribution raised to the three-quarters power so rare words show up a bit more.


Q54: How does GloVe differ from Word2Vec?

In 30 seconds. “Word2vec is local — it slides a window and learns from individual context pairs. GloVe first builds the whole co-occurrence matrix, then fits vectors so the dot product plus two biases matches the log co-occurrence count. The motivating idea is that ratios of co-occurrence probabilities carry the meaning. In practice they perform about the same — and there’s a nice result showing skip-gram is implicitly factorizing a shifted PMI matrix, so they’re closer relatives than they look.”

The short version.

Word2VecGloVe
Statistics usedlocal, window-basedglobal co-occurrence matrix
Objectivepredict context (or centre)weighted least squares on
Optimizerstreaming SGDfits the pre-aggregated matrix
Memorytiny, streams the corpusmust hold the co-occurrence matrix
Key claimdistributional predictionpreserves co-occurrence ratios
Performanceoften similar; GloVe sometimes better

Why it works.

Both methods are trying to make the dot product of two word vectors reflect how often those words appear together, relative to how often you would expect by chance. Word2vec gets there implicitly, one pair at a time; GloVe writes the target down explicitly and does least squares on it. Think of it as sampling the corpus versus tabulating it — the same underlying statistics, reached by two different routes.

The math, and what it buys you.

Answer:

Word2Vec:

  • Uses local context (windows)
  • Predicts context from center word
  • Local statistics

GloVe:

  • Uses global co-occurrence matrix
  • Preserves co-occurrence ratios
  • Global statistics

GloVe Objective:

w_i · w_j + b_i + b_j ≈ log(X_ij)

Where X_ij = co-occurrence count

Training:

  1. Build co-occurrence matrix from entire corpus
  2. Weighted least squares to preserve ratios
  3. More efficient than Word2Vec

Key Insight: Preserves ratios: P(solid|ice) / P(solid|steam) ≈ P(gas|ice) / P(gas|steam)

Comparison:

  • Word2Vec: Local, window-based
  • GloVe: Global, matrix-based
  • Performance: Often similar, GloVe sometimes better

What the GloVe objective actually is, and where it comes from.

The line is the model; the loss that fits it is a weighted least-squares problem:

with the weighting function

Three details in there each solve a specific problem. The sum runs only over non-zero entries, so the cost is proportional to the number of observed co-occurrences rather than — the matrix is extremely sparse. The weight rising from zero stops rare, noisy co-occurrences from dominating a squared loss, and its cap at stops “the” from dominating instead. And the two bias terms absorb each word’s overall frequency, so the dot product is left to model the interaction rather than the marginals. There are separate centre and context vectors and ; the released vectors are their sum, which averages out initialization noise.

Why ratios, and why that forces a log. The paper’s starting observation is that raw co-occurrence probabilities are less meaningful than their ratios. is large, the same ratio for “gas” is small, and for a word related to both (“water”) or neither (“fashion”) it is near one — so the ratio is what isolates meaning. If you want vector differences to correspond to ratios, i.e. , and you want to turn a difference of vectors into a ratio of scalars, the exponential is essentially forced, which inverts to the log form above. Bringing this up shows you know GloVe is derived rather than guessed.

The honest modern verdict. The word2vec/GloVe distinction matters much less than either camp claimed: Levy and Goldberg showed skip-gram with negative sampling is implicitly factorizing a shifted PMI matrix, so both methods are matrix factorizations of co-occurrence statistics with different weightings and different optimizers. Tuned carefully, they land within noise of each other on most benchmarks. Both are also static embeddings — one vector per word type, so “bank” gets a single vector blending the river and the money sense — which is the limitation that contextual models removed in 2018 and the reason neither is a first choice for new work today.

Good and bad.

GloVe: uses global statistics directly, trains fast per epoch on the aggregated matrix, and has a derivation you can defend. But building the matrix is a memory-hungry full corpus pass, and it is still a static embedding.

Word2Vec: streams with almost no memory, parallelizes trivially, and handles very large corpora comfortably. But it only ever sees local windows, and it is also static.

Both: one vector per word type, no subword information, corpus biases baked in — the limitations that contextual models removed in 2018.

Follow-up: Which trains faster? GloVe’s per-epoch cost is low because it works over the pre-aggregated co-occurrence matrix, but building that matrix is a full corpus pass with substantial memory. Word2vec streams the corpus with almost no memory and parallelizes trivially via asynchronous SGD. On a very large corpus word2vec is usually the more practical choice.

Why the interviewer asks this. They want to hear “global versus local statistics,” but the answer that stands out explains that both are ultimately factorizing the same co-occurrence information.

Saying it out loud. Word2vec is local — it slides a window and learns from individual context pairs. GloVe first builds the whole co-occurrence matrix, then fits vectors so that the dot product plus two bias terms matches the log of the co-occurrence count, using a weighted least-squares loss that downweights both very rare and very frequent pairs. The motivating idea is that ratios of co-occurrence probabilities are what carry meaning. In practice they perform about the same, and there’s a nice result showing skip-gram is implicitly factorizing a shifted PMI matrix, so they’re closer relatives than they look. Both are static — one vector per word regardless of context — which is exactly what BERT and friends fixed.


Q55: Explain the evolution of NLP embeddings.

In 30 seconds. “The thread running through all of it is the distributional hypothesis — you know a word by the company it keeps — and each generation is a better way to compress that co-occurrence information. TF-IDF stores it raw and sparse, word2vec and GloVe squash it into a few hundred dense dimensions, and ELMo and BERT made it contextual so ‘bank’ gets a different vector in a river sentence than a finance one. The one thing I’d push back on is calling the old stuff obsolete — BM25 is a TF-IDF descendant and it’s still in production everywhere.”

The short version.

EraMethodRepresentationWhat it added
1970sTF-IDFsparse, -dimstatistical term weighting
1980s–90sN-gramscount-basedlocal sequence modelling
2013Word2Vecdense, staticsemantics from local windows
2014GloVedense, staticglobal co-occurrence statistics
2018+ELMo / BERT / GPTdense, contextualone vector per occurrence
2020+LLMs, CLIPcontextual, multimodalinstruction tuning, RLHF, images

The four axes of movement: sparse → dense, local → global → contextual, fixed → context-dependent, single modality → multimodal.

Why it works.

Each step removes an assumption the previous one was forced to make. Sparse vectors assume every word is unrelated to every other; dense vectors drop that. Static vectors assume a word means one thing everywhere; contextual vectors drop that. Multimodal models drop the assumption that meaning lives only in text. Nothing here is a different idea — it is the same idea with fewer constraints each time.

The math, and what it buys you.

Answer:

Timeline:

1. TF-IDF (1970s):

  • Statistical weighting
  • Sparse, high-dimensional
  • No semantic understanding

2. N-grams (1980s-1990s):

  • Sequence modeling
  • Count-based probabilities
  • Local context only

3. Word2Vec (2013):

  • Neural embeddings
  • Dense, low-dimensional
  • Semantic relationships
  • Fixed embeddings (no context)

4. GloVe (2014):

  • Global co-occurrence
  • Matrix factorization
  • Better than Word2Vec on some tasks

5. Contextual Embeddings (2018+):

  • ELMo: Bidirectional LSTM
  • BERT: Transformer, bidirectional
  • GPT: Transformer, unidirectional
  • Context-dependent embeddings

6. Modern LLMs (2020+):

  • Large-scale language models
  • Multimodal (CLIP, GPT-4V)
  • Instruction tuning, RLHF

Key Evolution:

  • Sparse → Dense
  • Local → Global → Contextual
  • Fixed → Context-dependent
  • Single modality → Multimodal

The thread that connects the whole timeline.

The one sentence that ties these six stages together is the distributional hypothesis: a word’s meaning is characterized by the company it keeps (Firth, 1957). Every method on this list is a different answer to “how do I compress a word’s co-occurrence statistics?” — TF-IDF stores them raw and sparse, word2vec and GloVe factorize them into a few hundred dimensions, and contextual models compute them on the fly per occurrence. Saying that out loud reframes a list of names into a single idea with a progression, which is what the question is really after.

What each transition actually bought you.

Sparse to dense (TF-IDF → word2vec). A TF-IDF vector has one dimension per vocabulary word and is almost entirely zeros, so “car” and “automobile” are exactly orthogonal — cosine similarity zero, no matter how interchangeably they are used. Dense embeddings put them in nearby directions because they appear in similar contexts. The cost is interpretability: a TF-IDF dimension means “the word insulin,” a word2vec dimension means nothing you can name.

Static to contextual (word2vec → ELMo/BERT). A static embedding must average all senses of a word into one vector, so “bank” sits somewhere between rivers and money and is a good representation of neither. Contextual models run the whole sentence through the network and emit a different vector for each occurrence. ELMo did this with stacked bidirectional LSTMs, concatenating a left-to-right and a right-to-left pass; BERT did it with a Transformer trained by masked language modelling — hide 15% of tokens and predict them from both sides at once, which gives genuinely joint bidirectional conditioning rather than two independently-trained directions stitched together.

Contextual to generative-and-general (BERT → GPT-family). BERT gives you good representations that still need a task-specific head and a fine-tuning run. Decoder-only models fold the task itself into the input as text, so one frozen model handles many tasks.

One correction worth making to the timeline. TF-IDF and n-grams are listed as if superseded, and they are not. BM25, the direct descendant of TF-IDF, is still a competitive retrieval baseline in 2026 and is half of every serious hybrid search stack (Q68–Q70). The honest framing is “added to the toolbox,” not “replaced.”

Where embeddings sit today. Modern text embedding models are Transformer encoders — often initialised from a decoder-only LLM — trained contrastively on hundreds of millions of query–document pairs with hard negatives, typically producing 384 to 3072 dimensions, and frequently supporting Matryoshka representations where you can truncate the vector to a shorter prefix and keep most of the quality, which is a real cost lever when you are storing hundreds of millions of vectors. This layer of the stack turns over every few months; treat any specific model name or leaderboard position as a snapshot and verify before quoting it.

Saying it out loud. The thread running through all of it is the distributional hypothesis — you know a word by the company it keeps — and each generation is a better way to compress that co-occurrence information. TF-IDF stores it raw and sparse, so “car” and “automobile” are literally orthogonal. Word2vec and GloVe squash it into a few hundred dense dimensions so similar words land near each other. Then ELMo and BERT made it contextual, so “bank” gets a different vector in a river sentence than in a finance sentence. And now embeddings are contrastively trained Transformer encoders. The one thing I’d push back on is calling the old stuff obsolete — BM25 is a direct TF-IDF descendant and it’s still in production everywhere.


Q56: How do you evaluate multimodal models?

In 30 seconds. “Three buckets: retrieval quality — Recall@1/5/10 in both directions plus median rank, because a model can be lopsided; downstream transfer — zero-shot classification and VQA; and robustness and fairness. The thing I’d raise unprompted is contamination: these models train on web-scale image-text data and the benchmarks are on the web too, so ‘zero-shot’ means zero-shot on the task format, not necessarily on the data.”

The short version.

  • Zero-shot classification — prompt per class, nearest match, accuracy.
  • Image-text retrieval — R@1/5/10 both directions, median rank.
  • VQA — accuracy broken out by question type, since reasoning types differ wildly.
  • Robustness — distribution shift, adversarial and natural variation.
  • Bias and fairness — performance gaps across gender, race, culture.
  • Few-shot transfer — how quickly it adapts from a handful of examples.

Why it works.

No single number describes a multimodal model, because it has two front doors. Retrieval metrics test whether the shared space is well-organized; classification tests whether it transfers; robustness tests whether the organization survives inputs unlike the training distribution. Reporting only one is like judging a bilingual speaker on their vocabulary in one language.

The math, and what it buys you.

Answer:

1. Zero-Shot Image Classification:

  • Create text prompts for classes
  • Find most similar prompt
  • Measure accuracy

2. Image-Text Retrieval:

  • Text → Image: Find images matching query
  • Image → Text: Find captions matching image
  • Metrics: Recall@K, Median Rank

3. Visual Question Answering:

  • Answer questions about images
  • Requires reasoning
  • Accuracy by question type

4. Robustness:

  • Distribution shifts
  • Adversarial examples
  • Natural variations

5. Bias Evaluation:

  • Gender, racial, cultural biases
  • Fairness across groups

6. Few-Shot Learning:

  • Learn from few examples
  • Transfer learning capability

Best Practices:

  • Multiple metrics
  • Diverse datasets
  • Human evaluation when possible
  • Error analysis
  • Bias testing

Making the metrics concrete.

Recall@K is the fraction of queries for which the correct item appears anywhere in the top results. For image–text retrieval the convention is to report R@1, R@5 and R@10 in both directions, because a model can be strong at text-to-image and weak at image-to-text and a single number hides that. Median rank is the median position of the correct item across queries; it is more robust to a handful of catastrophic queries than the mean rank, which a single result at rank 4000 can destroy.

The evaluation trap specific to multimodal models: contamination. These models are pretrained on hundreds of millions of web image–text pairs, and the standard benchmarks — COCO, Flickr30k, and the popular VQA sets — are also on the web. “Zero-shot” is only zero-shot with respect to the task format, not necessarily with respect to the data. Bringing this up unprompted is a strong signal. The mitigations are near-duplicate detection between the pretraining corpus and the eval set (perceptual hashing or embedding-space nearest neighbours), and evaluating on held-out sets built after the pretraining cutoff.

Two more axes the list above misses.

Prompt sensitivity. A CLIP-style model’s zero-shot accuracy can swing several points depending on whether you write “cat,” “a photo of a cat,” or “a photo of a cat, a type of pet.” A single-number accuracy therefore does not describe the model, it describes the model plus one prompt. Report a mean and spread over a template set, or use prompt ensembling and say so.

Hallucination and grounding for generative multimodal models. For a model that produces text about an image, retrieval metrics do not apply at all. What you want is whether the described objects are actually present — object-hallucination probes such as asking yes/no existence questions about objects that are and are not in the image — and whether claims are attributable to image regions. This is a different failure mode from a classification error and needs its own measurement.

A practical evaluation protocol. Pick one held-out retrieval set for representation quality, one classification set for zero-shot transfer, one adversarial or distribution-shifted set for robustness, and a small human-rated set of a few hundred examples for anything generative. Freeze the prompts, version the eval set, and track all of it over time; the failure to catch in production is a slow drift, and you cannot see drift without a fixed yardstick.

Follow-up: Your zero-shot accuracy dropped after a model update but retrieval R@1 improved. What now? Usually a prompt-distribution mismatch rather than a genuine regression: the new model’s text encoder responds differently to your class templates. Re-tune the templates or use prompt ensembling before concluding the model got worse.

Why the interviewer asks this. Evaluation is where multimodal projects quietly fail, so this question separates people who have shipped one from people who have read the papers.

Saying it out loud. I’d split it into three buckets. Retrieval quality — Recall at 1, 5 and 10 in both directions, image-to-text and text-to-image, plus median rank, because a model can be lopsided. Downstream transfer — zero-shot classification on held-out sets, VQA if it’s generative. And robustness and fairness — distribution shift, and bias across demographic groups. The thing I’d raise unprompted is contamination: these models are trained on web-scale image-text data and the benchmarks are on the web too, so “zero-shot” means zero-shot on the task format, not necessarily on the data. I’d also report prompt sensitivity, because zero-shot numbers move several points just from rewording the template.


See 38_multimodal_and_embeddings/ for detailed explanations!


RAG (Retrieval-Augmented Generation)

Q57: Design a RAG system. What are the key components?

In 30 seconds. “I’d split it into two loops that run on totally different clocks. Offline: parse, chunk, embed, store vectors with metadata. Online, per query: embed the query with the same model, run ANN plus BM25, fuse, re-rank the top fifty with a cross-encoder down to five, stuff those into the prompt with source markers, generate. The two retrieval stages exist because a bi-encoder is cheap and precomputable but imprecise, and a cross-encoder is accurate but costs a forward pass per candidate — cheap for recall, expensive for precision.”

The short version.

LoopRunsStepsClock
Offline (indexing)on ingest / scheduleload → clean → chunk → embed → store with metadataminutes to hours
Online (serving)per queryrewrite → embed → retrieve (dense + sparse) → fuse → re-rank → assemble → generate → citea few hundred ms to seconds

The nine components in order: ingestion, chunking, embedding, query processing, retrieval, re-ranking, context assembly, generation, post-processing.

Why it works.

RAG is a system-design problem wearing an ML costume. The core insight is a funnel: start with millions of candidates and cheap scoring, end with five candidates and expensive scoring. Each stage is allowed to be sloppier than the next because the next one cleans up after it — recall first, precision last. Get that ordering wrong (expensive model early) and the system is unaffordable; skip the last stage and it is inaccurate.

The math, and what it buys you.

Answer:

RAG Components:

1. Document Ingestion:

  • Load documents (PDF, DOCX, HTML, etc.)
  • Extract text, metadata
  • Preprocess and clean

2. Chunking:

  • Split documents into chunks
  • Strategies: fixed-size, sentence-based, semantic
  • Overlap between chunks (10-20%)

3. Embedding Generation:

  • Generate embeddings for chunks
  • Use embedding models (sentence-transformers, OpenAI)
  • Store in vector database

4. Query Processing:

  • Process user query
  • Generate query embedding
  • Query expansion/rewriting

5. Retrieval:

  • Vector similarity search
  • Hybrid search (dense + sparse)
  • Metadata filtering
  • Top-K retrieval

6. Re-ranking (Optional):

  • Cross-encoder for accuracy
  • Re-rank top results
  • Better precision

7. Context Assembly:

  • Select top-K chunks
  • Order by relevance
  • Fit in context window

8. Generation:

  • LLM with context
  • Prompt engineering
  • Generate answer

9. Post-processing:

  • Extract answer
  • Generate citations
  • Validate answer

Pipeline:

Query → Embedding → Retrieval → Re-ranking → Context → Generation → Answer

Walkthrough: what happens to one query, end to end.

The component list above is the what; here is the when, which is what a design interview is actually probing. The system has two loops that run on completely different clocks, and saying so early is the single best structural move you can make.

The offline loop (indexing), run on ingest or on a schedule. Documents arrive; you parse them (PDFs are the hard case — layout, tables, and scanned pages all need different handling); you chunk them (Q63); you embed each chunk with an embedding model, which is a network mapping text to a fixed-length vector such that semantically similar text lands nearby; you write vectors plus the original text plus metadata into a vector index. The metadata is not optional — chunk ID, source document, section, page, timestamp, and access-control tags all need to travel with the vector, because filtering and citation both depend on them later, and retrofitting them means a full re-index.

The online loop (serving), run per query in a few hundred milliseconds. Rewrite the query if needed (resolving “what about last year?” against conversation history is a real and commonly-skipped step); embed it with the same model used for indexing — different models produce incompatible spaces, and mixing them silently returns garbage rather than erroring; run an approximate nearest neighbour search for the top 50–100 candidates alongside a BM25 keyword search; fuse the two lists; re-rank the fused top ~50 with a cross-encoder down to the top 5; assemble those into a prompt with explicit source markers; call the LLM; then post-process to attach citations and check for unsupported claims.

Why the two-stage retrieve-then-rerank split exists. A bi-encoder embeds query and document separately, so document vectors can be computed once, offline, and searched in milliseconds — but the query never gets to interact with the document text, so precision suffers. A cross-encoder concatenates query and document and runs them jointly through a Transformer, which is far more accurate because every query token can attend to every document token, but it costs a full forward pass per candidate and cannot be precomputed. Scoring a million documents with a cross-encoder is impossible; scoring fifty is a few tens of milliseconds. So you use the cheap model for recall and the expensive one for precision. That is the entire architectural argument, and it generalizes to almost every large-scale ranking system.

A latency budget to have in your pocket. Roughly: query embedding 5–20 ms, ANN search 5–50 ms depending on index and filter selectivity, cross-encoder re-rank of 50 candidates 20–100 ms on a GPU, and then generation, which dominates everything at hundreds of milliseconds to several seconds and scales with output length, not input length. The consequence worth stating: if you need to cut latency, cut output tokens first — trimming retrieval is optimizing the wrong term.

Cost, and where it actually goes. Embedding a corpus is a one-time cost and is cheap: at roughly $0.02 per million tokens for a small commercial embedding model as of August 2026, a 100-million-token corpus costs about $2 to embed once. Generation is the recurring cost and is orders of magnitude larger, because you pay per query and every retrieved chunk is input tokens you are billed for. Vector storage is the third line item and is easy to underestimate: one million chunks at 1536 dimensions in float32 is about 6 GB of raw vectors before index overhead, which is why dimension reduction, product quantization, or Matryoshka truncation to 256–512 dimensions is standard at scale. All prices here are time-sensitive — verify current rates before using them in a real design document.

Good and bad.

What RAG buys you: fresh knowledge without retraining, citations and auditability, per-document access control, and far cheaper updates than fine-tuning.

What it costs you: a whole extra distributed system to operate (index freshness, deletes, compaction), latency added before the first token, retrieval failures that surface as confident wrong answers, and a hard dependency on chunking and embedding choices that are painful to change after you have indexed a corpus.

Follow-up: How do you handle document updates and deletions? Key chunks by a stable (document_id, chunk_index) and store a content hash. On re-ingest, re-embed only chunks whose hash changed, and delete by document_id prefix. Also check that your vector store supports real deletes rather than tombstones only — some index types (HNSW in particular) mark deleted entries and need periodic compaction, and a system that never compacts slowly fills with ghosts.

Why the interviewer asks this. It is a system-design question wearing an ML costume; they are watching for whether you separate offline indexing from online serving and whether you think about staleness, cost, and failure modes rather than reciting a pipeline diagram.

Saying it out loud. I’d split it into two loops that run on totally different clocks. Offline, you parse documents, chunk them, embed each chunk, and store the vectors with metadata like source, section, and access tags — metadata you’ll wish you had later for filtering and citations. Online, per query, you embed the query with the same model, do a fast approximate nearest-neighbour search plus a BM25 keyword search, fuse the two lists, re-rank the top fifty with a cross-encoder down to about five, stuff those into the prompt with source markers, and generate. The reason for the two retrieval stages is that a bi-encoder is cheap and can be precomputed but imprecise, while a cross-encoder is accurate but costs a forward pass per candidate — so cheap for recall, expensive for precision.


Q58: How do you improve RAG retrieval accuracy?

In 30 seconds. “First thing I’d do is figure out which failure I have. Build a small labelled set and check whether the right chunk is in the top hundred. If it isn’t, that’s a recall problem and re-ranking can’t save you — you fix chunking, add hybrid search, or generate query paraphrases. If it is in the top hundred but not the top five, that’s a ranking problem and a cross-encoder fixes it in an afternoon. Diagnose before you stack techniques.”

The short version.

SymptomDiagnosisFix
Right chunk not in top 100recall failurechunking, hybrid search, query expansion, multi-query
In top 100, not in top 5ranking failurecross-encoder re-ranker, learning-to-rank
Right chunk retrieved, bad answercontext failuresmall-to-big retrieval, better prompt assembly
Right chunk not in corpus at allingestion bugfix the pipeline, not the retriever

The full menu: better chunking, better embeddings, hybrid search, re-ranking, query expansion, metadata filtering, multi-stage retrieval.

Why it works.

Retrieval is a funnel and each stage can only make things worse, never better, for anything the previous stage dropped. A re-ranker is a proofreader: it can reorder the shortlist beautifully but it cannot summon a document that never made the shortlist. That asymmetry is why the recall-versus-ranking diagnostic comes first and everything else second.

The math, and what it buys you.

Answer:

1. Better Chunking:

  • Semantic chunking (respect boundaries)
  • Hierarchical chunking
  • Multi-granularity chunks
  • Overlapping chunks

2. Better Embeddings:

  • Domain fine-tuning
  • Hybrid embeddings (dense + sparse)
  • Multi-vector embeddings
  • Query-specific embeddings

3. Hybrid Search:

  • Dense retrieval (semantic)
  • Sparse retrieval (BM25, keywords)
  • Weighted combination
  • Better coverage

4. Re-ranking:

  • Cross-encoder (more accurate)
  • Learning-to-rank
  • Multi-stage retrieval
  • Better precision

5. Query Expansion:

  • Synonym expansion
  • Related terms
  • Query rewriting
  • Multi-query generation

6. Metadata Filtering:

  • Filter by document type
  • Filter by date, source
  • Improve precision

7. Multi-Stage Retrieval:

  • Stage 1: Coarse (ANN, top-100)
  • Stage 2: Re-rank (top-10)
  • Stage 3: Fine-grained (top-5)

How to actually do this, in priority order.

The list above is a menu; an interviewer wants a method. The method is: measure first, because retrieval failures split into two kinds with opposite fixes, and treating them the same is how teams waste a quarter.

Step 1 — separate recall failures from ranking failures. Build a small evaluation set of 100–200 real queries with the correct chunk labelled. Then for each query check whether the right chunk is anywhere in the top 100. If it is not, you have a recall problem and no amount of re-ranking will help — re-ranking can only reorder what retrieval already found. If it is in the top 100 but not the top 5, you have a ranking problem and a cross-encoder will likely fix it in an afternoon. This single diagnostic is the highest-value thing in this answer.

Step 2 — fix recall problems at the source. Recall failures usually trace to one of four causes. Chunks are too small, so the answer is split across a boundary — fix with larger chunks or overlap. Chunks are too large, so the relevant sentence is diluted by 900 irrelevant tokens and the embedding drifts — fix with smaller chunks or by embedding a summary while retrieving the full chunk. Vocabulary mismatch, where the user says “cannot log in” and the document says “authentication failure” — fix with hybrid search so the dense side handles paraphrase, or with query expansion. Or the information genuinely is not in the corpus, which is an ingestion bug, not a retrieval one, and is worth ruling out before anything else.

Step 3 — add a cross-encoder re-ranker. This is reliably the best effort-to-improvement ratio in the whole list. Retrieve 50–100 candidates cheaply, score each jointly with the query, keep the top 5. Expect a solid jump in precision@5 for tens of milliseconds of GPU time.

Two techniques worth naming explicitly, since they come up constantly.

HyDE (Hypothetical Document Embeddings) has the LLM write a plausible fake answer to the query, then embeds that and searches with it. It works because a query and its answer often look nothing alike in embedding space — “how do I reset my password?” versus a paragraph of instructions — whereas a hypothetical answer looks a lot like a real one. It costs an extra LLM call, so it suits low-QPS, high-value queries.

Multi-query and RAG-fusion. Have the LLM produce three or four paraphrases of the query, retrieve for each, and fuse the result lists with Reciprocal Rank Fusion (see Q69). This covers vocabulary variation cheaply and is one of the few techniques that helps recall rather than just ranking.

Parent-document / small-to-big retrieval deserves a mention too: index small, precise chunks so matching is sharp, but return the surrounding parent section to the LLM so the context is complete. It resolves the “small chunks retrieve better, large chunks answer better” tension directly, and it is what most mature production systems end up doing.

Follow-up: When is fine-tuning the embedding model worth it? When your domain vocabulary is genuinely unlike the pretraining distribution — legal citations, clinical abbreviations, internal product codenames — and you have or can mine a few thousand query–positive pairs, for instance from click logs or by having an LLM generate questions from each chunk. Below a few thousand pairs you will usually get more from a re-ranker. Also budget for the fact that changing the embedding model means re-embedding the entire corpus.

Why the interviewer asks this. They want to know whether you debug retrieval with measurement or by stacking techniques hopefully.

Saying it out loud. First thing I’d do is figure out which failure I have. Build a small labelled set and check whether the right chunk is in the top hundred. If it isn’t, that’s a recall problem and re-ranking can’t save you — you fix chunking, add hybrid search for vocabulary mismatch, or generate query paraphrases. If it is in the top hundred but not the top five, that’s a ranking problem and a cross-encoder re-ranker usually fixes it in an afternoon. Beyond that, the two things I reach for most are hybrid dense-plus-BM25, because keyword search catches exact IDs and product names that embeddings fumble, and small-to-big retrieval, where you index tight chunks for precision but hand the model the surrounding section so it has enough to answer.


Q59: How do you handle context window limits in RAG?

In 30 seconds. “Stop treating it as a fitting problem and treat it as a selection problem — you want the fewest chunks that contain the answer, not the most chunks that fit. Re-rank, take three to five, dedupe, and reserve budget for the system prompt and the answer itself. One near-free trick: put the strongest chunk first and the second-strongest last, because models attend better to the beginning and end than the middle. And I’d push back on just using a million-token window — accuracy often drops when you add mediocre chunks.”

The short version.

  • Priority selection — sort by relevance, take top-K, apply a score floor.
  • Summarize or compress overflow — prefer extractive compression over abstractive rewriting.
  • Merge and dedupe — overlapping chunks waste budget on repeated sentences.
  • Dynamic / iterative retrieval — fetch more only if the first pass is insufficient.
  • Long-context models — available, but not a free pass; see below.

Why it works.

Every chunk you add is both evidence and noise. The model has to find the answer and ignore everything else, and it is imperfect at the second job, so past a certain point each extra chunk costs more in distraction than it adds in coverage. Think of it as briefing a colleague before a meeting: three sharp pages beat a fifty-page binder, even though the binder technically contains more.

The math, and what it buys you.

Answer:

1. Priority-Based Selection:

  • Sort by relevance score
  • Take top-K until context full
  • Truncate if needed

2. Summarization:

  • Summarize chunks that don’t fit
  • Hierarchical summarization
  • Preserve key information

3. Chunk Merging:

  • Merge related chunks
  • Remove redundancy
  • Create coherent context

4. Dynamic Context:

  • Adaptive chunk selection
  • Iterative retrieval
  • Expand if needed

5. Long-Context Models:

  • Use models with larger context (32K, 100K+)
  • More expensive but better
  • Less truncation

Best Practice:

  • Prioritize by relevance
  • Summarize overflow
  • Use appropriate context size

The part that changes the answer: long context is not the same as good context.

The list above treats “use a long-context model” as the easy escape hatch, and in 2026 context windows of 200K to 1M tokens are widely available, with some models advertising more. But two effects mean you should not simply dump everything in.

Lost in the middle. Model accuracy on retrieving a fact from a long context is strongly position-dependent: highest when the relevant text is near the beginning or the end, measurably worse when it is buried in the middle. This is a robust, repeatedly-reproduced finding. The practical consequence is an ordering rule — put your best chunk first and your second-best last, and let the mediocre ones fill the middle. That is a free accuracy gain and costs one line of code.

Precision falls as you add context. Every irrelevant chunk you include is a distractor the model must ignore, and models are imperfect at ignoring. Adding chunks 6 through 20 often lowers answer accuracy even though recall went up. Feeding fewer, better chunks generally beats feeding more.

There is also the blunt economic point: input tokens are billed, and attention cost grows quadratically in sequence length (Q89), so a 100K-token prompt is both expensive and slow. Context window sizes and prices move constantly — check current model specs rather than trusting a number written down months ago.

A concrete assembly algorithm. Given a token budget for context, reserve room for the system prompt, the question, and the expected answer first — a common bug is budgeting only the input and then getting truncated mid-generation. Then walk the re-ranked chunks in score order, adding each if it fits, and stopping when the budget is spent or the relevance score falls below a floor. That floor matters: a chunk scoring 0.2 is more likely to mislead than to help, so it is better to send fewer chunks than to fill the window because you can. Finally, deduplicate — overlapping chunks from adjacent regions of the same document waste budget on repeated sentences.

When summarization is and is not appropriate. Compressing overflow chunks with an LLM adds a call, adds latency, and adds a place for facts to get mangled. It earns its keep for genuinely long single documents where the answer needs the whole arc — “summarize the legal argument across these 40 pages.” It is the wrong tool for factual lookup, where losing the exact number or date is the one failure you cannot tolerate. Contextual compression — extracting only the query-relevant sentences from each chunk rather than abstractively rewriting it — is the safer middle ground, because it can drop text but cannot invent it.

Follow-up: How do you pick the number of chunks ? Empirically, by sweeping against end-to-end answer accuracy on your eval set, not by picking a round number. The curve almost always rises then falls, and the peak is often lower than people expect — frequently 3 to 5 chunks rather than 10 or 20.

Saying it out loud. The first move is to stop thinking of it as a fitting problem and start thinking of it as a selection problem — you want the fewest chunks that contain the answer, not the most chunks that fit. I’d re-rank, take the top three to five, dedupe overlapping text, and reserve budget for the system prompt and the answer itself, which people forget. One trick that’s basically free: put the strongest chunk first and the second strongest last, because models reliably attend better to the beginning and end than the middle. And I’d push back on just using a million-token window — accuracy often goes down when you add mediocre chunks, because each one is a distractor, and you’re paying for every token.


Q60: How do you prevent hallucination in RAG?

In 30 seconds. “I’d separate three different bugs, because they have different fixes. If the right chunk was never retrieved, the model falls back on pretraining memory — that’s a retrieval bug. If the chunk was there and the model still misstated it, that’s unfaithfulness, and prompting, forced citations and post-hoc checking are the tools. And if the answer just isn’t in the corpus, ‘I don’t know’ has to be an explicitly allowed output — models abstain far more when abstention is named as a valid answer.”

The short version.

FailureWhat happenedFix lives in
Retrieval failureright chunk never retrieved; model used parametric memoryretrieval (Q58)
Unfaithful generationchunk was present, answer misstates itprompting, citations, verification
Unanswerable answeredcorpus lacks the answer; model invents oneexplicit sanctioned abstention

Defence layers: grounded prompt, per-sentence citations, claim-level entailment check, retrieval-based confidence, extractive fallback, flag-and-escalate.

Why it works.

The model is always doing the same thing — producing the most plausible continuation. Grounding does not change that; it changes what “plausible” is conditioned on. Every technique here is either putting the right evidence in front of it, or making unsupported text easy to detect afterwards. Citations are the load-bearing trick precisely because they are checkable: an invented claim with a source marker is a lie you can catch mechanically.

The math, and what it buys you.

Answer:

1. Prompt Engineering:

"Answer ONLY based on the provided context.
If the answer is not in the context, say 'I don't know'."

2. Answer Validation:

  • Check if answer supported by context
  • Extract supporting sentences
  • Confidence scoring

3. Citation Generation:

  • Link answer to source chunks
  • Show supporting evidence
  • Enable fact-checking

4. Confidence Scoring:

  • Model confidence in answer
  • Retrieval confidence
  • Combined confidence score

5. Answer Extraction:

  • Extract answer from context
  • Don’t generate new information
  • Use extractive QA models

6. Post-Processing:

  • Validate answer against context
  • Check for contradictions
  • Flag uncertain answers

Naming the failure precisely, because the fix depends on which one it is.

“Hallucination” in RAG covers three distinct bugs and they have different remedies. Distinguishing them is most of the answer.

Retrieval failure. The right chunk was never retrieved, so the model answered from parametric memory — what it absorbed during pretraining — instead of from your documents. Nothing in the generation stage fixes this; it is a Q58 problem.

Unfaithful generation. The correct chunk was in context and the model still said something the chunk does not support — a subtle number change, a merged fact, an over-confident generalization. This is what prompt discipline, citation requirements, and post-hoc verification target.

Unanswerable question answered anyway. The corpus genuinely does not contain the answer and the model produces something plausible rather than abstaining. This is the one that needs an explicit escape hatch and an explicit reward for using it.

A prompt pattern that measurably helps. Beyond “answer only from the context,” three specific instructions do real work: require every sentence to carry a source marker like [chunk_3], so an unsupported claim has nowhere to hide; tell the model to quote the supporting span verbatim before stating the conclusion, which forces it to locate evidence rather than reconstruct it from memory; and make “the provided context does not contain this information” an explicitly sanctioned, named output rather than an implicit option. Models abstain far more readily when abstention is presented as a correct answer rather than a failure.

Verification, mechanically. After generation, split the answer into atomic claims and check each against the retrieved context. Three implementations, in increasing cost and accuracy: string or n-gram overlap between claim and context (fast, catches only blatant invention); a natural language inference model, which classifies a (premise, hypothesis) pair as entail / contradict / neutral, run with the chunk as premise and the claim as hypothesis (this is the standard approach and a small NLI model is cheap); or an LLM-as-judge call asking whether each claim is supported. Anything scoring below threshold gets stripped or flagged. This is essentially what the faithfulness metric in RAG evaluation frameworks computes, so the same machinery serves as both a guardrail and a metric.

A calibration caveat worth raising. Token-level model confidence is a poor proxy for factual correctness — models are frequently fluent and confident while wrong, and log-probabilities measure fluency more than truth. If you want a usable confidence signal, derive it from retrieval scores and from self-consistency (sample the answer several times at non-zero temperature and check whether the samples agree on the load-bearing facts) rather than from the generator’s own probabilities.

Follow-up: Does strict grounding hurt anything? Yes, and you should say so. A tightly grounded model refuses more often, including on questions it could have answered correctly by combining context with common sense. There is a real precision/recall dial here, and where you set it depends on domain: in clinical or legal settings a false abstention is far cheaper than a false claim; in an internal help bot the reverse may hold.

Why the interviewer asks this. Hallucination is the reason RAG systems fail in production, so they want to hear a layered defence rather than a single prompt trick.

Saying it out loud. I’d first separate three different bugs, because they have different fixes. If the right chunk was never retrieved, the model falls back on what it memorized during pretraining — that’s a retrieval bug, not a generation bug. If the chunk was there and the model still misstated it, that’s unfaithfulness, and that’s where prompting, forced citations, and post-hoc checking help. And if the answer just isn’t in the corpus, you need “I don’t know” to be an explicitly allowed answer, not an implicit one — models abstain a lot more when you name abstention as a valid output. For verification I’d split the answer into atomic claims and run a small entailment model against the retrieved chunks, flagging anything that isn’t entailed.


Q61: How do you evaluate a RAG system?

In 30 seconds. “Evaluate the two halves separately so a bad number tells you where to look. Retrieval: recall@K and NDCG against queries with the right chunk labelled. Generation: skip BLEU and ROUGE — n-gram overlap punishes correct paraphrases and rewards fluent hallucinations that reuse the reference’s words — and use the faithfulness / answer-relevance / context-precision / context-recall breakdown instead. Context recall low means fix retrieval; context recall fine but faithfulness low means fix the prompt.”

The short version.

StageMetricsA bad score means
RetrievalPrecision@K, Recall@K, MRR, MAP, NDCG@Kchunking, embeddings, hybrid search
Generationfaithfulness, answer relevance (not BLEU/ROUGE)prompt, model, grounding
Diagnosticcontext precision, context recallwhich of the two above to fix
End to endcorrectness, completeness, citation qualitythe whole pipeline

Why it works.

An end-to-end score tells you the system is broken; it never tells you which part. Splitting the metrics along the pipeline seam turns evaluation into a bisection search. Context recall is the single most useful number here because it is measured after retrieval and before generation — it cleanly assigns blame.

The math, and what it buys you.

Answer:

Retrieval Metrics:

  • Precision@K: Precision of top-K
  • Recall@K: Recall of top-K
  • MRR: Mean reciprocal rank
  • MAP: Mean average precision
  • NDCG@K: Normalized discounted cumulative gain

Generation Metrics:

  • BLEU: N-gram overlap
  • ROUGE-L: Longest common subsequence
  • BERTScore: Semantic similarity
  • Answer accuracy: Correctness

End-to-End Metrics:

  • Answer relevance: Is answer relevant?
  • Answer correctness: Is answer correct?
  • Answer completeness: Is answer complete?
  • Citation quality: Are citations correct?

Best Practices:

  • Use multiple metrics
  • Combine automated + human evaluation
  • Monitor in production
  • Task-specific evaluation

Splitting the evaluation so a failure points at a component.

The metric list above is correct but flat. What makes it usable is organizing it so a bad number tells you which stage to fix. Evaluate retrieval and generation separately, then end to end.

Retrieval, with the metrics defined. Precision@K is the fraction of the returned that are relevant; Recall@K is the fraction of all relevant chunks that made it into the top . MRR — mean reciprocal rank — averages , so it cares only about how quickly you find one good answer and is the right metric when a single chunk suffices. NDCG@K — normalized discounted cumulative gain — sums graded relevance discounted by and divides by the best achievable value, so it handles multi-level relevance and rewards putting the best item first. Use MRR for lookup-style questions, NDCG when several chunks contribute and their order matters.

Generation: why BLEU and ROUGE are near-useless here, and what to use instead. BLEU and ROUGE measure n-gram overlap with a reference answer. For RAG that is the wrong measurement twice over: a correct answer phrased differently scores badly, and a fluent hallucination that reuses the reference’s vocabulary scores well. The field has largely moved to reference-free, LLM-judged decompositions, which are worth knowing by name because they will come up:

  • Faithfulness — of the claims in the answer, what fraction are supported by the retrieved context? This is the hallucination metric.
  • Answer relevance — does the answer address the question that was asked, rather than a neighbouring one?
  • Context precision — of the retrieved chunks, what fraction were actually needed?
  • Context recall — of the facts in the ground-truth answer, what fraction were present in the retrieved context?

The last two are diagnostic gold: low context recall means fix retrieval, high context recall with low faithfulness means fix generation or prompting.

How to get a test set without a labelling budget. Have an LLM read each chunk and generate a question answerable from it alone; the source chunk becomes the ground-truth retrieval label and the chunk text the reference answer. This bootstraps hundreds of examples in an hour. State the limitation too, because a good interviewer will press: synthetic questions are easier and more literal than real user questions, they rarely span multiple chunks, and they inherit the generator’s biases. Use synthetic data to catch regressions, and a smaller hand-curated set drawn from real query logs for absolute judgements.

Beware LLM-as-judge artefacts. Judges show position bias (favouring the first option shown), verbosity bias (favouring longer answers), and self-preference (favouring text from the same model family). Mitigate by randomizing presentation order, calibrating the judge against a few hundred human labels before trusting it, and — where the stakes justify it — using a judge from a different model family than the generator.

Follow-up: What do you monitor in production, where there are no labels? Proxies: retrieval score distributions (a drift downward means queries are moving away from your corpus), abstention rate, citation coverage, answer length, latency percentiles, and explicit user feedback. Sample a small fraction of live traffic for offline human or judge review — a hundred conversations a week catches most regressions long before a user complains.

Why the interviewer asks this. Anyone can build a RAG demo; knowing how to tell whether it is getting better is the part that requires having done it.

Saying it out loud. I’d evaluate the two halves separately so a bad number tells me where to look. For retrieval, recall at K and NDCG against a set of queries with the right chunk labelled. For generation, I’d skip BLEU and ROUGE — n-gram overlap punishes correct paraphrases and rewards fluent hallucinations that reuse the reference’s words. Instead I’d use the RAGAS-style breakdown: faithfulness, which asks what fraction of the answer’s claims are supported by the retrieved context; answer relevance; and context precision and recall. Context recall low means fix retrieval; context recall fine but faithfulness low means fix the prompt or the model. For a test set I’d bootstrap with LLM-generated questions per chunk, but I’d keep a hand-curated set from real logs too, because synthetic questions are always easier than real ones.


Q62: What are common RAG challenges and solutions?

In 30 seconds. “The textbook list is chunking, embedding quality, retrieval accuracy, context limits, hallucination, scale and cost. The ones that actually bite in production are different: access control, which has to filter during retrieval not after; staleness, because the index is a cache that needs change detection and real deletes; and conflicting sources, where an old policy and its replacement are both retrievable and equally plausible. And the most common cause of a disappointing RAG system is bad ingestion — mangled PDFs and flattened tables — not the model.”

The short version.

ChallengeSolution
Chunking strategysemantic / hierarchical chunking, overlap
Embedding qualitydomain fine-tuning, hybrid embeddings
Retrieval accuracymulti-stage retrieval, re-ranking, hybrid search
Context window limitspriority selection, compression, long-context models
Hallucinationgrounded prompts, citations, claim validation
ScalabilityANN search, sharding, caching
Costfewer retrieved tokens, caching, model routing
Access controlpre-filtered ANN search, permissions in metadata
Stalenesscontent hashes, incremental re-embed, real deletes
Conflicting sourceseffective dates and versions, recency in ranking

Why it works.

Almost every item above is the same lesson in a different costume: a RAG system is a cache of your knowledge, and caches fail in cache-shaped ways — stale entries, missing invalidation, permissions computed at the wrong layer, and cost that scales with how much you copy around. Debugging one is much closer to debugging a search stack than to debugging a model.

The math, and what it buys you.

Answer:

1. Chunking Strategy:

  • Challenge: How to split documents
  • Solution: Semantic chunking, hierarchical, overlap

2. Embedding Quality:

  • Challenge: Domain-specific semantics
  • Solution: Fine-tuning, hybrid embeddings

3. Retrieval Accuracy:

  • Challenge: Retrieved chunks not relevant
  • Solution: Multi-stage retrieval, re-ranking, hybrid search

4. Context Window Limits:

  • Challenge: Too many chunks, can’t fit
  • Solution: Priority selection, summarization, long-context models

5. Hallucination:

  • Challenge: Model generates wrong info
  • Solution: Prompt engineering, citations, validation

6. Scalability:

  • Challenge: Large document sets
  • Solution: ANN search, distributed systems, caching

7. Cost:

  • Challenge: High API costs
  • Solution: Self-hosted models, caching, batch processing

Three challenges the list misses, which are usually the ones that bite in production.

Access control and multi-tenancy. If different users may see different documents, retrieval must filter by permission before or during the search, not after. Filtering after means the ranking was computed over documents the user cannot see, so results become sparse and inconsistent — and if any snippet leaks into a prompt, you have a data breach rather than a quality bug. Most vector databases support pre-filtered ANN search; check that yours does it natively and understand the recall cost, because aggressive filters can force the index to search a much larger fraction of the graph and latency degrades sharply as filters get selective.

Freshness and staleness. A vector index is a cache of your documents and it goes stale like any cache. You need change detection on the source, incremental re-embedding keyed by a content hash, real deletes, and — critically — a plan for what happens when you change embedding models, which invalidates every vector in the store. Budget a full re-index as a routine operation rather than an emergency.

Conflicting sources. Real corpora contain a 2023 policy document and its 2026 replacement, both retrievable and both plausible. The model has no way to know which wins. The fixes are metadata-driven: attach effective dates and version numbers at ingest, prefer recent documents in ranking, and surface the conflict to the user (“two sources disagree; the current policy says…”) rather than silently picking one. This one comes up in almost every real deployment and almost never in tutorials.

On cost, with numbers. The cost line above says “high API costs” without saying where they go. Concretely, embedding is one-time and small — on the order of $0.02 per million tokens for a small commercial model as of August 2026, so a large corpus costs single-digit dollars to embed. Generation is per-query and dominates: if each query sends 4,000 input tokens of retrieved context and produces 300 output tokens, your per-query cost is set almost entirely by the model tier and the context size. The three levers that actually move the bill are, in order: send fewer retrieved tokens; cache aggressively (both exact-match response caching for repeated queries and provider-side prompt caching for a stable system prompt, which can cut input cost substantially); and route easy queries to a smaller model, reserving the frontier model for hard ones. These figures are time-sensitive — verify current pricing before quoting it.

Follow-up: What is the single most common cause of a disappointing RAG system? Chunking and ingestion quality, not model choice. Badly parsed PDFs, tables flattened into word salad, headers and footers repeated into every chunk, and boilerplate that dominates the embedding will cap your ceiling no matter how good the retriever is. Reading fifty random chunks by eye is an unglamorous hour that finds more bugs than a week of hyperparameter tuning.

Saying it out loud. The textbook list is chunking, embedding quality, retrieval accuracy, context limits, hallucination, scale, and cost. The ones that actually bite in production, though, are a bit different. Access control — you have to filter by permission during retrieval, not after, or your ranking is computed over documents the user can’t see. Staleness — the index is a cache, and it needs change detection, real deletes, and a plan for the day you swap embedding models and invalidate everything. And conflicting sources, where an old policy and its replacement are both retrievable and equally plausible; you fix that with effective dates in metadata and recency in ranking. And honestly the most common cause of a bad RAG system is just bad ingestion — mangled PDFs and flattened tables — not the model.


See 39_rag_retrieval_augmented_generation/ for detailed implementations!


Q63: Explain different chunking strategies. When to use each?

In 30 seconds. “It comes down to one tradeoff: small chunks retrieve well but answer badly, and big chunks answer well but retrieve badly — small gives you a sharp embedding, big averages into mush. My default is recursive splitting that respects structure first, around 500 tokens with 10–15% overlap. But the real fix is to decouple the two: index small units for precision, then hand the model the surrounding parent section so it has enough to actually answer.”

The short version.

StrategySplit onUse whenMain weakness
Fixed-sizecharacter countprototyping, uniform textbreaks sentences
Sentence-basedsentence boundariesnarrative text, common defaultsplitter errors
Paragraph-basedparagraph breaksstructured, long-form docsvariable sizes
Semanticembedding-similarity droptopic-shifting docs, accuracy-criticalslow, costly, often no win
Recursivestructure, descendinggeneral purpose (LangChain default)more complex
Sliding windowfixed size + stridecode, sequential dataredundancy, many chunks
Token-basedtoken countLLM budgeting, cost controltokenizer-specific
Hierarchicaldoc → section → paracomplex/academic docsstorage, complexity
Content-awareper content typemixed text/code/tablesneeds type detection
Metadata-enrichedany + rich metadatacitations, filteringmore storage

Defaults worth memorizing: start sentence-based or recursive; 256–1024 tokens, 512 typical; 10–20% overlap; upgrade to semantic only if measurement says so.

Why it works.

Chunking is deciding what the unit of meaning is. An embedding is an average, so a chunk covering one idea produces a vector that points somewhere specific, while a chunk covering six ideas produces a vector that points at their centroid — a place none of the six actually live. That is the entire reason chunk size dominates retrieval quality, and the reason structural boundaries (headers, paragraphs, function definitions) beat arbitrary ones: someone already decided where one idea stopped.

The math, and what it buys you.

Answer:

1. Fixed-Size Chunking:

  • Split into fixed-size chunks (e.g., 512 chars)
  • Overlap between chunks (10-20%)
  • Use: Simple documents, prototyping, uniform content
  • Pros: Simple, fast
  • Cons: Breaks sentences, no semantic awareness

2. Sentence-Based Chunking:

  • Split on sentence boundaries
  • Group sentences into chunks
  • Use: Narrative text, general documents, production (common default)
  • Pros: Respects boundaries, better coherence
  • Cons: Sentence splitting can be imperfect

3. Paragraph-Based Chunking:

  • Split on paragraph boundaries
  • Use: Structured documents, academic papers, long-form
  • Pros: Preserves structure, natural units
  • Cons: Variable sizes, may be too large/small

4. Semantic Chunking:

  • Use embeddings to find semantic boundaries
  • Split when semantic shift detected
  • Use: High accuracy needs, topic-based documents
  • Pros: Best semantic coherence, optimal retrieval
  • Cons: Slower, requires embeddings, higher cost

5. Recursive Chunking:

  • Hierarchical splitting (paragraphs → sentences → words)
  • Use: General-purpose, variable structure, production (LangChain default)
  • Pros: Robust, handles any structure
  • Cons: More complex, can be slow

6. Sliding Window:

  • Fixed-size with stride (overlap)
  • Use: Sequential data, code, long documents
  • Pros: Preserves context, good for sequential
  • Cons: Many chunks, redundancy

7. Token-Based:

  • Split by token count (not characters)
  • Use: LLM systems, accurate sizing, cost optimization
  • Pros: Accurate for LLMs, precise control
  • Cons: Requires tokenizer, model-specific

8. Hierarchical:

  • Multi-level (document → section → paragraph)
  • Use: Complex documents, academic papers
  • Pros: Preserves structure, multi-level retrieval
  • Cons: Complex, more storage

9. Content-Aware:

  • Different strategy per content type (code, tables, text)
  • Use: Mixed content, technical docs, research papers
  • Pros: Optimal per type, handles complexity
  • Cons: Very complex, requires detection

10. Metadata-Enriched:

  • Chunks with rich metadata (section, page, etc.)
  • Use: Structured docs, citations, filtering
  • Pros: Rich context, better filtering
  • Cons: More storage, complex

Best Practices:

  • Start: Sentence-based or recursive
  • Upgrade: Semantic if accuracy critical
  • Overlap: 10-20% of chunk size
  • Size: 256-1024 tokens (512 common)
  • Test: Evaluate retrieval accuracy

How to actually choose, rather than picking from the menu.

The decision reduces to one tension. Small chunks retrieve precisely, because the embedding of 100 tokens about one topic is a sharp point in the space, but they answer badly, because the surrounding sentence that resolves a pronoun or supplies the units is missing. Large chunks answer well and retrieve poorly, because averaging 1,500 tokens of mixed content produces a vague vector that is close to everything and specific to nothing. Every strategy on the list is an attempt to get one without paying for the other.

The move that resolves it: decouple the retrieval unit from the generation unit. Index small (a sentence, a paragraph, or an LLM-written summary), retrieve on that, then hand the model the parent — the surrounding section or full document. This is variously called parent-document retrieval, small-to-big, or sentence-window retrieval, and it is what most mature systems converge on. Mentioning it moves the answer from “I know the list” to “I have built one.”

Overlap, concretely. With 512-token chunks and 15% overlap you carry about 77 tokens from the previous chunk into the next, so a fact straddling a boundary appears whole in at least one chunk. The cost is storage and near-duplicate results, which is why you deduplicate at assembly time (Q59).

Structure beats heuristics whenever structure exists. Markdown headers, HTML sections, docstrings, and function boundaries are ground truth about where topics begin and end, and they are free. Split on them first, and only fall back to length-based splitting inside an oversized section. Recursive character splitting is exactly this idea generalized: try paragraph breaks, then sentence breaks, then whitespace, then raw characters, descending only when a piece is still too big.

On semantic chunking, an honest caveat. It sounds obviously better — embed each sentence, cut where consecutive similarity drops — and in published comparisons it wins less often and by less than expected, while costing an embedding call per sentence at ingest. It is worth trying, but “we use semantic chunking because it is more principled” is not a result. Measure it against recursive splitting on your own data before adopting it.

Content types that break naive chunking, which is where real corpora live. Tables must never be split across chunks, and a table separated from its caption and column headers is unusable — serialize each row with its headers, or keep the table whole and attach a text summary for embedding. Code should be split on function or class boundaries with the imports and enclosing signature carried along. Long lists and procedures should keep their numbered steps together, since half a procedure is worse than none. And headers, footers, and navigation boilerplate should be stripped before chunking; if the same 40-token footer appears in every chunk, it contributes to every embedding and pulls the whole corpus toward a common, useless direction.

Follow-up: How do you evaluate a chunking strategy without rebuilding everything? Fix a labelled query set, then measure recall@50 for each candidate strategy — it isolates chunking from ranking, since re-ranking cannot recover a chunk that retrieval never surfaced. Re-indexing a few thousand documents under three strategies is an afternoon, and the winner is usually visible immediately.

Why the interviewer asks this. Chunking is where most of a RAG system’s quality is actually decided, so a candidate who treats it as a preprocessing detail has probably not debugged one.

Saying it out loud. The whole thing comes down to one tradeoff: small chunks retrieve well but answer badly, and big chunks answer well but retrieve badly. Small chunks give you a sharp embedding; big chunks average into mush. So my default is recursive splitting that respects structure first — headers, paragraphs, then sentences — at maybe five hundred tokens with ten or fifteen percent overlap. But the real fix is to decouple the two: index small units for precision, then hand the model the surrounding parent section so it has enough to actually answer. And I’d treat tables and code specially — never split a table from its header row, and split code on function boundaries — because that’s where naive chunking silently destroys the corpus.


See 39_rag_retrieval_augmented_generation/chunking_strategies.md for complete guide!


Linear and Logistic Regression Derivations

Q64: Derive linear regression from first principles. Explain intuitively.

In 30 seconds. “Define the error at each point as actual minus predicted, square it, sum, and set both partial derivatives to zero. The intercept equation immediately says the line passes through the mean of x and the mean of y. Substitute that back and the slope comes out as covariance over variance — ‘how much do x and y move together, normalized by how much x moves on its own.’ Geometrically you’re projecting y onto the column space of X.”

The short version.

  • Model: ; error per point is .
  • Loss: sum of squared errors — positive, differentiable, and the MLE under Gaussian noise.
  • Solve: set and .
  • Answer: , and .
  • Matrix form: , the normal equation.

Why it works.

Two pictures, and they agree. Algebraically you are asking “what slope makes the residuals uncorrelated with x?” — and covariance over variance is exactly the answer to that. Geometrically, is a point in -dimensional space and the fitted values live in the low-dimensional plane spanned by the columns of ; the closest point in a plane to an outside point is its perpendicular projection, so the residual must be orthogonal to every feature. “Drop a perpendicular” and “set the derivative to zero” are the same instruction in two languages.

The math, and what it buys you.

Answer:

Goal: Find line y = wx + b that best fits data

Step 1: Define Error

  • For each point: error = yᵢ - (wxᵢ + b)
  • Actual value minus predicted value

Step 2: Cost Function (MSE)

  • Sum of squared errors: MSE = (1/n) Σ (yᵢ - wxᵢ - b)²
  • Why squared? Always positive, penalizes large errors, differentiable

Step 3: Minimize

  • Take derivatives, set to zero
  • ∂MSE/∂b = 0 → b = ȳ - wx̄ (line passes through center!)
  • ∂MSE/∂w = 0 → w = Σ(xᵢ - x̄)(yᵢ - ȳ) / Σ(xᵢ - x̄)²

Final Solution:

w = Σ(xᵢ - x̄)(yᵢ - ȳ) / Σ(xᵢ - x̄)²  (covariance / variance)
b = ȳ - wx̄

Intuition:

  • Slope = how much y changes per unit x (covariance/variance)
  • Intercept = mean y - slope × mean x
  • Line passes through center of data (x̄, ȳ)

Matrix Form:

w = (XᵀX)⁻¹Xᵀy  (normal equation)

Why it works:

  • Minimizes sum of squared errors (geometrically optimal)
  • Projects y onto column space of X
  • Unique solution (if data not degenerate)

The derivative steps, fully worked.

The answer above jumps from “take derivatives, set to zero” straight to the solution. Here is what happens in between, because that gap is exactly what a whiteboard interview asks you to fill.

Write the cost as a sum rather than an average — dividing by scales the whole function by a positive constant and cannot move the minimizer, so dropping it is legal and keeps the algebra clean:

Differentiating with respect to . Treat as fixed. Each term is a square of something whose derivative with respect to is , so by the chain rule:

Set to zero and divide by : . Divide through by and use :

The interpretation is worth stating: the fitted line passes exactly through the centroid , for any slope. That is why the residuals always sum to zero when the model has an intercept.

Differentiating with respect to . The inner derivative is now :

Substituting to eliminate it. Replace with :

The centering step that makes it recognizable. The useful identity here is that , so subtracting a constant multiple of it changes nothing: , and likewise . Substituting:

Confirm it is a minimum. , , and the determinant of the Hessian is whenever the are not all identical. Positive definite Hessian, so it is the unique global minimum — and the degenerate case (all equal) is exactly the case where no slope is identifiable, which the algebra reports as a zero denominator.

A relationship worth having ready. Since , the slope is . If you standardize both variables the slope is the correlation. That is a one-line answer to “what is the relationship between regression and correlation?”

Why squared error and not absolute error. Three separate reasons, and they are different: squared error is differentiable everywhere while has a kink at zero; it has a closed-form solution while absolute error requires linear programming; and it is the maximum-likelihood loss under Gaussian noise (Q47). The cost is sensitivity to outliers, since a residual of 10 contributes 100 while a residual of 1 contributes 1. Minimizing absolute error gives you the conditional median instead of the conditional mean, which is the robust alternative and is what quantile regression generalizes.

Numerically verified. On 40 simulated points with true slope 2.5 and intercept 1.3, the covariance-over-variance formula gives slope 2.4754, intercept 1.3888, and solving the normal equation gives the identical pair to ten decimal places — as it must, since they are the same equations written differently.

Follow-up: When would you not use the normal equation? When is large, since forming and inverting costs and becomes impractical past a few thousand features; when is ill-conditioned, where you should use a QR or SVD-based solver instead of an explicit inverse for numerical stability; or when the data does not fit in memory, where SGD is the answer. In practice you should essentially never compute a matrix inverse explicitly — np.linalg.solve or lstsq is both faster and more stable than inv(A) @ b.

Why the interviewer asks this. Linear regression is the smallest model where you can demonstrate the full loop — assumption, loss, gradient, closed form, geometry — so it is a proxy for whether you can derive anything at all.

Saying it out loud. You start with a line and define the error at each point as actual minus predicted. Square it, because squaring is differentiable, penalizes big misses more, and — this is the real reason — it’s the maximum-likelihood loss if you assume Gaussian noise. Sum over points, then take partial derivatives with respect to slope and intercept and set both to zero. The intercept equation immediately tells you the line goes through the mean of x and the mean of y. Substitute that back in and the slope comes out as covariance over variance — which reads as “how much do x and y move together, normalized by how much x moves on its own.” Geometrically you’re projecting y onto the column space of X, so the residual ends up orthogonal to every feature.


Q65: Derive logistic regression. Why sigmoid function?

In 30 seconds. “I start from the constraint that I need an output between zero and one. A linear model can output anything, so instead of modelling the probability I model the log-odds, which does range over all the reals. Solve that for p and the sigmoid falls out — it isn’t an arbitrary choice, it’s the inverse of the logit. Then the Bernoulli likelihood gives cross-entropy, and the sigmoid derivative cancels the log-loss denominator exactly, leaving the gradient as just prediction minus target times x.”

The short version.

  • Link: model ; invert to get .
  • Sigmoid: bounded in , smooth, , saturates at both ends.
  • Loss: negative log-likelihood of a Bernoulli = cross-entropy, and it is convex.
  • Gradient: — literally “error times input.”
  • Boundary: exactly when , so the decision boundary is linear.

Why it works.

The problem with putting a line straight onto a probability is that lines are unbounded and probabilities are not. The fix is to change what you are modelling rather than what model you use: odds live in , and log-odds live in — the same range a linear function produces. So you model the log-odds linearly and transform back. The sigmoid is not a design choice, it is the algebra of undoing that transform.

The math, and what it buys you.

Answer:

Problem: Need probabilities (0 to 1), not continuous values

Step 1: Log Odds

  • Odds = P / (1-P) (can be any positive number)
  • Log odds = log(P / (1-P)) (can be any real number)
  • Model: log(P / (1-P)) = wx + b (linear!)

Step 2: Solve for P

  • P / (1-P) = e^(wx + b)
  • P = e^(wx + b) / (1 + e^(wx + b))
  • P = 1 / (1 + e^(-(wx + b))) = σ(wx + b)

Why Sigmoid?

  • Bounded: Always between 0 and 1
  • Smooth: Differentiable everywhere
  • S-shaped: Good for probabilities
  • When wx + b → -∞: P → 0
  • When wx + b = 0: P = 0.5
  • When wx + b → +∞: P → 1

Step 3: Likelihood

  • P(y|x) = σ(wx + b)^y × (1 - σ(wx + b))^(1-y)
  • Likelihood = ∏ᵢ P(yᵢ|xᵢ)

Step 4: Cost Function (Cross-Entropy)

  • Maximize likelihood = Minimize negative log-likelihood
  • J(w, b) = -Σ [y log σ(wx + b) + (1-y) log(1 - σ(wx + b))]

Step 5: Gradient

  • ∂J/∂w = Σ [σ(wx + b) - y] × x
  • ∂J/∂b = Σ [σ(wx + b) - y]
  • Error = predicted - actual

Update:

w = w - α × Σ [σ(wx + b) - y] × x
b = b - α × Σ [σ(wx + b) - y]

Decision Boundary:

  • P(y=1|x) = 0.5 when wx + b = 0
  • Line wx + b = 0 separates classes

Why Cross-Entropy?

  • Optimal for classification (information theory)
  • Convex (guaranteed global minimum)
  • Works well with probabilities

The gradient derivation, with the step everyone skips.

The answer above states without showing why the sigmoid derivative and the log both vanish so cleanly. That cancellation is the point of the question, so here it is in full.

Fact 1 — the sigmoid derivative. With , apply the quotient (or chain) rule to :

using . This identity is why logistic regression is cheap: you already have from the forward pass, so the derivative costs one multiply.

Fact 2 — differentiate the loss for one example. Let with , and let the per-example loss be . Differentiate with respect to first:

Fact 3 — chain through , and watch the cancellation.

The factors cancel exactly. Then gives

and summing over the dataset reproduces the answer above. In matrix form, .

Why this cancellation is the whole argument for cross-entropy. Try squared error instead: gives , and that extra factor goes to zero whenever is near 0 or 1. So a confidently wrong prediction — when — produces a gradient of about and the model barely moves. Cross-entropy’s gradient in that case is : proportional to the error, with no saturation. That is the real reason for the loss choice, and it is a strictly better answer than “cross-entropy is information-theoretically optimal.”

On convexity, precisely. The Hessian of the summed cross-entropy is where . Every , so for any vector , — positive semi-definite, hence convex, hence no local minima. It is strictly convex when has full column rank. Note also that there is no closed-form solution, because setting leaves inside a nonlinear function; you solve it iteratively with gradient descent or Newton’s method (the latter, applied here, is exactly iteratively reweighted least squares).

The failure mode to mention: separable data. If a hyperplane perfectly separates the classes, the likelihood can always be increased by scaling up — pushing every closer to 0 or 1 — so the maximum is at infinity and the weights diverge. Any L2 penalty fixes this by bounding , which is one more reason regularized logistic regression is the default in every library.

Verified numerically. For a 50-example, 3-feature problem, the analytic gradient and a central-difference numerical gradient agree to within .

Follow-up: Why the logit link specifically, and not probit? The logit is the canonical link for the Bernoulli family, which is what makes the gradient come out as the clean “prediction minus target” above and guarantees convexity. Probit (the Gaussian CDF) fits almost identically in practice — the curves differ mainly in the tails — but loses the closed-form gradient elegance and the log-odds interpretation that makes coefficients readable as odds ratios.

Why the interviewer asks this. The cancellation is the moment where loss choice, activation choice and gradient behaviour all connect, so it is a compact test of whether you understand backprop or merely use it.

Saying it out loud. I start from the constraint that I need an output between zero and one. A linear model can output anything, so instead of modelling the probability directly I model the log-odds, which does range over all the reals — set log p over one-minus-p equal to w-x plus b. Solve that for p and the sigmoid just falls out; it’s not an arbitrary choice, it’s the inverse of the logit. Then for the loss I write the Bernoulli likelihood and take negative log, which gives cross-entropy. The nice part is the gradient: the derivative of sigmoid is p times one-minus-p, and the derivative of the log loss has p times one-minus-p in the denominator, so they cancel exactly and you’re left with just prediction minus target times x. That cancellation is why cross-entropy beats squared error here — with squared error that factor survives, so a confidently wrong prediction gets an almost zero gradient and the model can’t correct itself.


Q66: Why can’t we use linear regression for classification?

In 30 seconds. “Two real problems. The output isn’t a probability — it can be negative or above one, so you can’t threshold it meaningfully. And more importantly, squared error keeps pushing on points that are already correct: a class-zero point predicted at minus three is right, but it still contributes a residual of nine, so the model rotates the boundary to reduce it and misclassifies points near the boundary. One thing I’d correct in the usual answer: least squares with a linear output is still convex, so ‘local minima’ isn’t the issue — the loss is just shaped wrong for the task.”

The short version.

Linear regressionLogistic regression
Output range
Probabilistic meaningnonecalibrated
LossMSE — penalizes confident-correct pointscross-entropy — ignores them
Threshold at 0.5arbitraryprincipled ()
Imbalanced classesdegrades badlyhandles with class weights

Why it works (and where it doesn’t).

The deep issue is that regression is trying to hit a target value, while classification only needs to get on the right side of a line. Squared error cannot tell those apart, so it treats “very confidently correct” as an error to be fixed. It is like grading a multiple-choice test by how close the pencil mark is to the centre of the correct bubble.

The math, and what it buys you.

Answer:

Problems:

1. Output Range:

  • Linear regression: Output can be any real number (-∞, +∞)
  • Classification: Need probabilities [0, 1]
  • Linear regression can give negative values or > 1

2. Interpretation:

  • Linear regression output doesn’t represent probability
  • Can’t interpret as “80% chance of class 1”

3. Loss Function:

  • MSE not optimal for classification
  • Doesn’t penalize misclassifications appropriately
  • Can get stuck in local minima

4. Decision Boundary:

  • Linear regression: Threshold at 0.5 (arbitrary)
  • No probabilistic interpretation
  • Doesn’t work well for imbalanced classes

Solution:

  • Use logistic regression (sigmoid)
  • Output is probability [0, 1]
  • Cross-entropy loss (optimal for classification)
  • Probabilistic interpretation

When Linear Regression Works for Classification:

  • Binary classification with balanced classes
  • When you just need a threshold
  • But logistic regression is almost always better

The mechanism behind “MSE is not optimal,” which is the part worth knowing.

The list above is right, but points 3 and 4 assert without explaining, and a follow-up will find that out. Two concrete mechanisms:

The gradient vanishes exactly where you need it most. Suppose you use a linear output with squared error and threshold at 0.5. A point of class 0 predicted at is already correctly classified, yet it contributes a residual of 9 to the loss and a large gradient — the model spends capacity dragging an already-correct point toward zero, rotating the boundary to accommodate it. This is the real content of “linear regression is sensitive to outliers in classification”: a single far-away but correctly-classified point can shift the decision boundary enough to misclassify points near it. Logistic regression’s loss is near zero for confidently correct points, so they exert almost no pull.

The claim about local minima is not right, and you should correct it. Squared error with a linear output is convex in — it is ordinary least squares — so there are no local minima. The genuine problems are the ones above (wrong loss shape, no probabilistic meaning, outlier sensitivity), not optimization getting stuck. If you pair squared error with a sigmoid output, then you do get a non-convex surface with flat saturated regions, which is a real optimization problem — but that is a different model from plain linear regression. Being precise about which combination has which problem is a strong signal.

One nuance that makes the “when it works” section honest. Least-squares classification is not merely a hack: for two classes with equal covariance, the least-squares fit to targets produces the same decision direction as Fisher’s linear discriminant, up to scale. So it can work respectably on well-behaved balanced binary problems. It degrades badly for more than two classes, though, where one-hot targets with least squares produce the classic masking problem — an intermediate class can be dominated by its neighbours and never predicted at all for any input.

Follow-up: Isn’t logistic regression also just a linear boundary? Yes — the decision boundary is a hyperplane in both cases. The difference is not the shape of the boundary but the loss used to place it and the calibrated probability you get out. If you need a nonlinear boundary you need features (polynomial, splines, kernels) or a different model class; switching from linear to logistic regression does not buy you one.

Saying it out loud. Two real problems. First, the output isn’t a probability — it can be negative or above one, so you can’t threshold it meaningfully or feed it to anything that expects a probability. Second, and this one matters more, squared error keeps pushing on points that are already correct. A class-zero point predicted at minus three is right, but it still contributes a residual of nine, so the model rotates the boundary to reduce it and can misclassify points near the boundary as a result. Logistic regression’s loss goes to almost nothing for confidently correct points, so they stop pulling. One thing I’d correct in the usual answer: least squares with a linear output is still convex, so “local minima” isn’t the issue — the issue is the loss is shaped wrong for the task.


Q67: Explain the relationship between linear and logistic regression.

In 30 seconds. “Both are generalized linear models — same linear predictor , different assumption about the response and a different link. Linear assumes Gaussian noise with an identity link; logistic assumes Bernoulli with a logit link, so the linear predictor is the log-odds and you sigmoid it to get a probability. That’s also why both gradients come out as prediction minus target times x — a general property of canonical links, not a coincidence.”

The short version.

Linear regressionLogistic regression
Outputcontinuous, probability,
Model
Response distributionGaussianBernoulli
Linkidentitylogit
LossMSEcross-entropy (neg. log-likelihood)
Solutionclosed form or GDiterative only
Coefficient meanschange in per unit change in log-odds; is an odds ratio

Shared: the same linear predictor, the same weights-and-bias parameterization, and the same “prediction minus target times input” gradient.

Why it works.

They are the same machine with a different dial on the front. The linear part does all the learning; the link function just translates the unbounded number it produces into whatever range the target actually lives in. Change the dial and you get Poisson regression for counts, or gamma regression for durations — same code, same optimizer, different link.

The math, and what it buys you.

Answer:

Similarities:

  • Both use linear combination: wx + b
  • Both learn weights w and bias b
  • Both use gradient descent (or closed-form for linear)

Differences:

Linear Regression:

  • Output: Continuous values (-∞, +∞)
  • Model: y = wx + b
  • Cost: MSE (sum of squared errors)
  • Solution: Closed-form (normal equation) or gradient descent

Logistic Regression:

  • Output: Probabilities [0, 1]
  • Model: P(y=1|x) = σ(wx + b)
  • Cost: Cross-entropy (negative log-likelihood)
  • Solution: Gradient descent (no closed-form)

Key Insight:

  • Logistic regression = Linear regression + sigmoid
  • Log odds are linear: log(P/(1-P)) = wx + b
  • Probabilities are sigmoid: P = σ(wx + b)

Visual:

Linear:     y = wx + b  (straight line)
            ↓
Logistic:   P = σ(wx + b)  (sigmoid curve)

Connection:

  • Both model linear relationships
  • Linear: Direct relationship
  • Logistic: Linear in log-odds space

The unifying frame: generalized linear models.

The cleanest way to say what these two share is that both are generalized linear models — GLMs. A GLM has three parts: a linear predictor ; a distribution for from the exponential family; and a link function connecting them by . Linear regression is the GLM with a Gaussian response and the identity link, . Logistic regression is the GLM with a Bernoulli response and the logit link, . Poisson regression is the same recipe with a count response and a log link. Naming this frame turns “logistic is linear plus a sigmoid” into a principle that also tells you what to do when the target is a count, a rate, or a duration.

Why both gradients look identical, which is not a coincidence. For linear regression, . For logistic regression, . Same form: prediction minus target, times the input. This holds for every GLM fitted with its canonical link — the link that makes the exponential-family natural parameter equal the linear predictor. It is a general theorem, not a lucky cancellation repeated twice, and it is why the same optimizer code works across all of them.

Where the analogy breaks. Three places worth naming, since “logistic = linear + sigmoid” is a useful slogan that hides real differences.

Closed form. Linear regression has one because its gradient is linear in . Logistic does not, because sits inside a nonlinear . You iterate — gradient descent, L-BFGS, or Newton’s method, which here is exactly iteratively reweighted least squares: at each step you solve a weighted least-squares problem with weights , so logistic regression is literally repeated linear regression.

Existence of the optimum. Linear regression always has a finite solution given full rank. Logistic regression’s optimum runs off to infinity on perfectly separable data (Q65), which is why the default in every serious library is regularized.

Interpretation of coefficients. In linear regression is “the change in per unit change in ,” in the units of . In logistic regression is a change in log-odds, so is an odds ratio: a coefficient of means the odds multiply by about per unit increase. People routinely misreport this as a change in probability, which is wrong — the effect on probability depends on where on the sigmoid you are, and is largest near and nearly nil in the tails.

Follow-up: So could I fit logistic regression by running linear regression on the log-odds of the labels? No, and it is a good trap. The labels are 0 and 1, so their empirical log-odds are . You would need grouped data with observed proportions in each group, which is the classical “logit transform then weighted least squares” approach, and it fails as soon as a group has a proportion of exactly 0 or 1. Maximum likelihood on the individual observations avoids the whole problem.

Why the interviewer asks this. They are probing whether you have a general framework or two memorised special cases; the GLM answer demonstrates the former in one sentence.

Saying it out loud. Both are generalized linear models — same linear predictor w-x plus b, different assumption about the response and a different link. Linear regression assumes Gaussian noise with an identity link, so the prediction is the linear predictor directly. Logistic assumes Bernoulli with a logit link, so the linear predictor is the log-odds and you sigmoid it to get a probability. That’s also why both gradients come out as prediction minus target times x — that’s a general property of canonical links, not a coincidence. Where they differ: linear has a closed form because the gradient is linear in w; logistic doesn’t, so you iterate. And the coefficients mean different things — in logistic, exponentiating a coefficient gives you an odds ratio, not a change in probability.


See 01_classical_ml/linear_regression_derivation.md and 01_classical_ml/logistic_regression_derivation.md for complete derivations!


RAG Retrieval Methods

Q68: Explain BM25. How does it differ from TF-IDF?

In 30 seconds. “BM25 is TF-IDF with two fixes. Term frequency saturates instead of growing linearly — one to two occurrences helps a lot, twenty to a hundred barely helps, and the whole factor is capped, so no amount of keyword stuffing lets one term run away with the score. And it normalizes by document length relative to the collection average, so three mentions in a short page counts for more than three in a long one. Defaults of and are remarkably robust.”

The short version.

TF-IDFBM25
Term frequencylinear — 20 occurrences scores 20×saturating, capped at
Document lengthusually ignored or crudeexplicit normalization via
IDF form (probabilistic)
Tunable knobsnone (saturation), (length)
Keyword stuffingvulnerablebounded, resistant
Where usedteaching, simple baselinesElasticsearch, Lucene, production

Why it works.

Both are asking “is this document about this term?” The difference is that BM25 encodes two pieces of common sense TF-IDF lacks. The first: the tenth mention of a word tells you far less than the second did — evidence has diminishing returns. The second: a long document mentions everything, so a mention in a short focused page is stronger evidence of aboutness. Each is one term in the formula, and each has a knob.

The math, and what it buys you.

Answer:

BM25 (Best Matching 25) is industry-standard sparse retrieval, improving upon TF-IDF.

BM25 Formula:

BM25(t, d) = IDF(t) × (f(t, d) × (k₁ + 1)) / (f(t, d) + k₁ × (1 - b + b × |d|/avgdl))

Key Improvements:

1. Term Frequency Saturation:

  • TF-IDF: Linear (10x → score = 10, 20x → score = 20)
  • BM25: Saturates (10x → 8.5, 20x → 9.2)
  • Prevents one term from dominating

2. Document Length Normalization:

  • Normalizes by document length
  • Prevents bias toward long documents

3. Better IDF:

  • BM25 IDF: log((N - df + 0.5) / (df + 0.5))
  • More robust

Use Cases:

  • Keyword-based search
  • Production systems (Elasticsearch, Lucene)
  • Better than TF-IDF in most cases

Reading the formula piece by piece.

is the raw count of term in document . is the document length in tokens and avgdl the mean length across the collection. controls how fast term frequency saturates (typical value 1.2, sometimes 1.5) and controls how strongly length normalization applies, from 0 (none) to 1 (full), with 0.75 the standard default.

The saturation term, with real numbers. Set and and consider a document of average length, so the bracket equals 1 and the factor is . Then:

1251020100
TF factor1.0001.3751.7741.9642.0752.174

Two things are visible. Going from 1 to 2 occurrences buys 0.375; going from 20 to 100 buys 0.099. And the factor is bounded above by , approached but never reached. That ceiling is the mathematically important point: no single term can ever dominate a BM25 score by repetition, which is precisely the keyword-stuffing attack that linear TF-IDF is vulnerable to.

A correction to the illustrative figures in the answer above. The numbers “10x → 8.5, 20x → 9.2” do not correspond to any standard setting — the term-frequency factor is capped at , so it cannot reach 8.5. Read them as a schematic illustration of “sublinear growth,” not as computed BM25 values; the table just above gives the actual figures for the standard parameters.

The length normalization term, with real numbers. With the same parameters, an IDF of 6.91 (a term in 1,000 of 1,000,000 documents) and : an average-length document scores , while a document four times the average length scores . Three mentions in a short focused page is stronger evidence of aboutness than three mentions in a long rambling one, and is the dial for how much you believe that.

The IDF change, and why the matters. BM25’s probabilistic IDF is . Note that this goes negative when a term appears in more than half the documents — which arguably makes sense (a term in 90% of documents is anti-evidence) but breaks scoring in practice, since a document could improve its score by dropping a common term. Real implementations, Lucene included, use , which is always positive. Knowing that the textbook formula and the shipped formula differ here is a nice detail.

(All figures above computed directly from the formula with , , , .)

Follow-up: Why is BM25 still competitive in 2026? Because it has zero training cost, no domain adaptation problem, is fully interpretable (“this document ranked high because it contains these three rare query terms”), handles rare exact strings — error codes, part numbers, proper nouns — that embeddings blur together, and inverts trivially to a sparse index. Its weakness is exactly one thing: it cannot match a query to a document that uses different words for the same idea. Which is why hybrid search exists (Q69).

Why the interviewer asks this. BM25 is a rare case of a hand-designed formula where every term has a defensible justification, so explaining it tests whether you can read a model rather than just call one.

Saying it out loud. BM25 is TF-IDF with two fixes. First, term frequency saturates instead of growing linearly — going from one occurrence to two helps a lot, twenty to a hundred barely helps at all, and the whole factor is capped at k1 plus one, so no amount of keyword stuffing lets one term run away with the score. Second, it normalizes by document length relative to the collection average, so three mentions in a short page counts for more than three mentions in a long one, with a parameter b controlling how aggressively. The defaults are k1 around 1.2 and b around 0.75 and they’re remarkably robust. It’s still a strong baseline because it needs no training and nails exact matches like error codes and part numbers that embeddings tend to smear together.


Q69: Explain hybrid search in RAG. How do you combine sparse and dense?

In 30 seconds. “Hybrid means running BM25 and dense retrieval in parallel and merging the two lists. It works because they fail on different queries — BM25 whiffs on paraphrase, where the user says ‘can’t sign in’ and the doc says ‘authentication failure’; dense whiffs on rare exact strings like error codes and part numbers. Uncorrelated failures are exactly when ensembling pays. For merging I’d default to Reciprocal Rank Fusion over a weighted score sum, because BM25 scores are unbounded and query-dependent while cosine similarities sit in a narrow band.”

The short version.

Sparse (BM25)Dense (embeddings)
Matches onexact termsmeaning
Strong atIDs, codes, names, rare stringsparaphrase, synonyms, intent
Fails onvocabulary mismatchrare exact tokens, OOV terms
Training needednoneyes (and domain adaptation)

Combining: retrieve top-K from each, then either weighted sum after normalization ( keyword-heavy, balanced, semantic) or — better — Reciprocal Rank Fusion, which needs no normalization at all.

Why it works.

An ensemble only helps when its members are wrong about different things. Two dense retrievers make the same mistakes; a dense retriever and a keyword retriever do not. That is the whole argument. The analogy: one reader who understands what you meant, and one who remembers exactly what you wrote — you want both in the room, and you trust the answer they agree on.

The math, and what it buys you.

Answer:

Hybrid Search combines BM25 (sparse) + Dense (embeddings).

Why:

  • BM25: Exact matches, keywords
  • Dense: Semantic similarity
  • Neither perfect alone → Combine!

How:

  1. Retrieve from both (top-K each)
  2. Normalize scores to [0, 1]
  3. Combine: Final = α × BM25 + (1-α) × Dense
  4. Re-rank by combined score

Weight Selection:

  • α = 0.7: More BM25 (keyword-heavy)
  • α = 0.5: Balanced (default)
  • α = 0.3: More dense (semantic)

Best Practice:

  • Normalize before combining
  • Tune α on validation set
  • Use for production systems

Why score normalization is harder than it sounds, and what people do instead.

The weighted-sum recipe above has a real problem: BM25 scores are unbounded and their scale depends on the query — a rare three-term query produces much larger scores than a common one-term query — while cosine similarities live in and, for a good embedding model, are usually squeezed into a narrow band like . Min-max normalizing within a single result list makes the top result 1.0 and the bottom 0.0 regardless of whether that list was excellent or useless, so a query where dense retrieval found nothing still contributes a confident-looking 1.0. That is why weighted score fusion needs care and per-query calibration.

Reciprocal Rank Fusion, the method most systems actually use. RRF throws the scores away and fuses ranks:

Because only ranks enter, no normalization is needed and the two systems’ incomparable score scales stop mattering. The constant damps the influence of the very top ranks: without it, rank 1 would be worth twice rank 2, which over-rewards a single retriever’s confident-but-wrong first result. A document appearing at a decent rank in both lists beats a document ranked first in one and absent from the other, which is exactly the consensus behaviour you want.

A worked example. BM25 returns [d3, d1, d7, d2]; dense returns [d7, d9, d1, d4]. With : d7 scores , d1 scores , and d3 — ranked first by BM25 but absent from the dense list — scores only . So the fused order is d7, d1, d3, and the two documents both systems liked outrank the one only BM25 found, despite that one having been rank 1. (Computed directly; d7 edges d1 because it holds ranks 3 and 1 versus d1’s 2 and 3.)

Why the combination beats either part — the actual mechanism. The two retrievers fail on disjoint query types. BM25 fails on paraphrase: query “can’t sign in,” document “authentication failure,” zero term overlap, zero score. Dense fails on rare exact strings: an error code like ERR_2049, a part number, a person’s surname, or a newly-coined product name is either out of vocabulary or embedded near other alphanumeric noise. Because the failures are uncorrelated, the union of the two candidate sets has substantially higher recall than either alone, and that recall gain is what the re-ranker then converts into precision. Ensembling only helps when the members fail differently, and here they demonstrably do.

Follow-up: Do you need to run both retrievers on every query? Not necessarily. Query routing — classify the query as keyword-like (contains quoted strings, IDs, code identifiers) versus natural-language and weight accordingly — saves latency. But hybrid on everything is the safer default, since the classifier becomes one more thing that can be wrong, and the cost of BM25 is small compared to the generation call that follows.

Saying it out loud. Hybrid means running BM25 and dense retrieval in parallel and merging the two result lists. The reason it works is that they fail on different queries — BM25 whiffs on paraphrase, where the user says “can’t sign in” and the doc says “authentication failure,” and dense whiffs on rare exact strings like error codes or part numbers that embeddings smear together. Uncorrelated failures are exactly when ensembling pays. For merging I’d default to Reciprocal Rank Fusion rather than a weighted score sum, because BM25 scores are unbounded and query-dependent while cosine similarities sit in a narrow band, so normalizing them onto a common scale is genuinely fiddly. RRF just uses ranks — one over sixty-plus-rank, summed — so the scale problem disappears, and documents both retrievers liked naturally float to the top.


Q70: When to use BM25 vs Dense vs Hybrid?

In 30 seconds. “I’d pick based on what the queries look like. Exact tokens that matter — error codes, part numbers, API names — BM25, because embeddings blur those and BM25 nails them. People describing what they want in their own words — dense, because that’s the paraphrase gap. Real users — hybrid, because real logs contain both in the same session. And I’d start with BM25 on day one regardless: it needs no training, and its failures tell you precisely what the dense side would have to fix.”

The short version.

BM25DenseHybrid
Best forexact tokens, IDs, citationsparaphrase, intent, cross-lingualmixed real-world traffic
Trainingnoneembedding model + domain tuningboth
Interpretableyesnopartly
Infra costinverted index onlyvector index + GPUtwo indexes + fusion
Verdictstart hereadd when semantics matterproduction default

Why it works.

The choice is a mapping from query characteristics to method, not a preference ordering. And one under-appreciated point: adding a cross-encoder re-ranker to plain BM25 often beats dense retrieval with no re-ranker — so if you only have budget for one addition, buy the re-ranker before the second retriever.

The math, and what it buys you.

Answer:

BM25:

  • Keyword queries, exact matching
  • Fast, interpretable
  • Start here

Dense:

  • Semantic queries, synonyms
  • Related concepts
  • When embeddings available

Hybrid:

  • Production systems
  • Mixed query types
  • Best overall performance
  • Industry standard

Recommendation:

  • Start: BM25
  • Add: Dense if semantic needed
  • Production: Hybrid

Choosing by query shape, with the reasoning attached.

The right way to answer this is not a preference ordering but a mapping from query characteristics to method, because that is what you would actually reason about on a real system.

Reach for BM25 when queries carry rare, exact tokens whose identity matters — error codes, SKUs, legal citations, API names, surnames, chemical formulae. Also when the corpus is small or highly specialized so there is no good embedding model for it, when you need to explain to a user or an auditor why a document ranked where it did, when the corpus updates constantly and re-embedding is a burden, or when you simply have no GPU. Its cost profile is unbeatable: an inverted index, no model, milliseconds.

Reach for dense when users describe what they want rather than naming it, when the corpus and the queries use different vocabularies for the same concepts, when you need cross-lingual matching (a multilingual embedding model retrieves German documents for an English query, which BM25 cannot do at all), or when queries are long and conversational so exact-term overlap is diluted.

Reach for hybrid when you have real users, because real query logs contain both kinds and usually in the same session. This is the production default and the answer expected for a design question.

The honest caveats. Hybrid is not free: two indexes to build, keep in sync, and monitor; a fusion step with parameters to tune; and roughly double the retrieval infrastructure. On a genuinely homogeneous workload — say, a semantic search over support articles where users never type identifiers — dense alone may match hybrid at half the operational cost. Measure before assuming.

Where re-ranking sits in this decision. Any of the three retrieval modes can feed a cross-encoder re-ranker, and in most published comparisons adding a re-ranker to BM25 alone beats dense retrieval without one. If you are choosing where to spend one unit of effort, a re-ranker is frequently the better buy than a second retriever.

Follow-up: What is the fastest useful thing to try first on a new corpus? BM25, on the same day, with no training. It gives you a baseline number, and — more usefully — reading its failures tells you exactly what dense retrieval would need to fix, which is a far better guide than starting with the fanciest option and having nothing to compare against.

Why the interviewer asks this. They want evidence you pick methods from problem characteristics rather than from recency, and the tell is whether you can name a case where the simple method wins.

Saying it out loud. I’d pick based on what the queries look like. If they contain exact tokens that matter — error codes, part numbers, API names — BM25, because embeddings blur those together and BM25 nails them. If people describe what they want in their own words and the corpus uses different vocabulary, dense, because that’s the paraphrase gap embeddings close. If it’s real users, hybrid, because real query logs contain both in the same session. And I’d start with BM25 on day one regardless, because it needs no training and its failures tell you precisely what the dense side would have to fix. One thing worth saying: adding a cross-encoder re-ranker on top of plain BM25 often beats dense retrieval with no re-ranker, so if I only had budget for one addition, it’d be the re-ranker.


See 39_rag_retrieval_augmented_generation/retrieval_methods.md for detailed explanations!


NLP Problems: Standard Solution Procedures

Q71: What’s the standard procedure for text classification?

In 30 seconds. “Before touching a model I’d pin down two things: single-label or multi-label, because that changes the output layer and the metrics, and what a mistake costs, because that sets the threshold. Then the usual pipeline — split first so nothing leaks, establish a dumb TF-IDF baseline so you know what ‘good’ means, then fine-tune a pretrained encoder, which wins even at a thousand labelled examples. For imbalance, move the threshold before doing anything clever.”

The short version.

PhaseWhat happens
1. Datacollect labels, handle imbalance, split train/val/test first
2. FeaturesTF-IDF (small), embeddings (medium), pretrained encoder (large)
3. Model<10K: TF-IDF + SVM · 10K–100K: neural or XGBoost · >100K: fine-tuned BERT
4. TrainingAdam, dropout, early stopping; BERT at lr 2e-5 for 3–5 epochs
5. EvaluationF1 (macro and micro), precision/recall, confusion matrix
6. DeploymentAPI, monitoring, drift detection, A/B test

Why it works.

The whole procedure is a ladder of baselines: each rung is cheap enough to build in a day and tells you whether the next rung is worth climbing. Skipping to the top means you have a number with nothing to compare it to, and no way to tell whether the model is learning the task or a shortcut in your data.

The math, and what it buys you.

Answer:

Phase 1: Data Preparation

  • Collect labeled data, handle class imbalance
  • Preprocessing: Lowercase, remove special chars, tokenize
  • Split: Train/Validation/Test

Phase 2: Feature Extraction

  • Small data: TF-IDF + Naive Bayes/SVM
  • Medium data: Word embeddings + LSTM/CNN
  • Large data: Fine-tuned BERT

Phase 3: Model Selection

  • < 10K: TF-IDF + SVM
  • 10K-100K: Embeddings + Neural or XGBoost
  • 100K: Fine-tuned BERT

Phase 4: Training

  • Traditional ML: Hyperparameter tuning
  • Neural: Adam optimizer, dropout, early stopping
  • BERT: Learning rate 2e-5, 3-5 epochs

Phase 5: Evaluation

  • Metrics: Accuracy, F1, Precision, Recall
  • Multi-class: Macro/Micro F1
  • Confusion matrix for analysis

Phase 6: Deployment

  • API endpoint
  • Monitoring, drift detection
  • A/B testing

What the phase list leaves out, and what to say first.

Before any of the six phases, spend a sentence on the two questions that determine everything downstream: what exactly is a label here, and what does a mistake cost? Multi-class (one label per document) and multi-label (any number of labels per document) need different output layers — softmax versus per-class sigmoid — different losses, and different metrics, and choosing wrong is a rewrite rather than a tweak. And an asymmetric error cost, say fraud detection where a miss costs a thousand times a false alarm, means accuracy is meaningless and your operating threshold is a business decision rather than 0.5.

Handling class imbalance, since the answer above names it without solving it. Four levers, in the order I would try them. Move the decision threshold — train normally, then pick the threshold on a validation set to hit your target precision or recall; this is free and often sufficient, and people skip straight past it. Weight the loss by inverse class frequency, which is one argument in most libraries. Resample, oversampling the minority or undersampling the majority, remembering to resample only the training split — resampling before the split leaks and produces a beautiful, fictional validation score. And for extreme imbalance use focal loss, which multiplies the cross-entropy of each example by where is the predicted probability of the true class, so easy well-classified examples contribute almost nothing and the gradient concentrates on hard ones.

On the data-size thresholds. The “under 10K use TF-IDF and SVM, over 100K fine-tune BERT” guidance is a reasonable starting heuristic, but it is worth saying out loud that transfer learning has shifted those boundaries down: fine-tuning a pretrained encoder frequently wins at 1,000 labelled examples, sometimes at a few hundred, precisely because the model arrives already knowing the language and only has to learn the task. And in 2026 the realistic first baseline for a new classification task is often a zero-shot or few-shot LLM prompt, which needs no labelled data at all — useful both as a baseline and as a way to generate labels to distil into a small, cheap model you can actually serve. This tradeoff shifts with model cost; re-check it rather than assuming.

One preprocessing correction. “Lowercase, remove special characters, remove stopwords” is inherited from the TF-IDF era and is actively harmful for a pretrained Transformer. Those models were pretrained on natural text with casing and punctuation intact, and their subword tokenizers depend on it — casing distinguishes “Apple” from “apple,” punctuation carries sentence structure, and stopwords carry syntax. For a Transformer, do essentially nothing beyond normalizing whitespace and stripping markup. Heavy preprocessing belongs with bag-of-words models.

Follow-up: Your test accuracy is 94% but production performance is poor. Where do you look first? Label leakage and distribution shift, in that order. Leakage: some feature (a template phrase, an ID, a timestamp artefact) correlates with the label in your dataset and not in the wild — check by looking at what the model attends to on its most confident predictions. Shift: your training data was sampled from a different period, source, or user population than production. Both are far more common than the model being wrong.

Saying it out loud. Before touching a model I’d pin down two things: is it single-label or multi-label, because that changes the output layer and the metrics, and what does a mistake cost, because that sets the threshold. Then the pipeline is the usual one — split first, so nothing leaks; establish a dumb baseline like TF-IDF plus logistic regression so you know what “good” means; then fine-tune a pretrained encoder, which these days wins even at a thousand labelled examples. For imbalance, I’d move the threshold before I did anything clever, then loss weighting, then resampling — and only resample the training split. And I’d skip the old lowercase-and-strip-stopwords routine for a Transformer; those models want the casing and punctuation they were pretrained on.


Q72: How do you solve NER? What’s the standard approach?

In 30 seconds. “NER is sequence labelling — tag every token with BIO tags like B-PER, I-PER, O — and the key point is that the labels aren’t independent. I-PER can’t follow O, so you either add a CRF layer that learns transition scores and decodes with Viterbi, or with a strong pretrained encoder use plain token classification and repair invalid transitions afterwards. The detail that trips people up is subword alignment. And evaluate with entity-level F1 — token accuracy is useless when ninety percent of tokens are O.”

The short version.

  • Format: BIO tags per token — B-PER I-PER O B-LOC for “Barack Obama visited Paris.”
  • Features: word + character/subword embeddings, surrounding context.
  • Model ladder: CRF → BiLSTM-CRF → fine-tuned encoder (token classification, lr 3e-5).
  • Pitfall: label the first subword of each word, mask the rest with .
  • Metric: entity-level F1, exact span and type match; report per type.
  • Hard cases: OOV (character embeddings), nested entities (span-based), ambiguity (more context).

Why it works.

The insight that separates NER from ordinary classification is that you are predicting a structure, not independent labels. “I-PER” is a claim about the token before it as much as about itself. Every technique here — the B/I distinction, the CRF transition matrix, Viterbi decoding, entity-level scoring — exists to take that dependence seriously.

The math, and what it buys you.

Answer:

Phase 1: Data Format

  • BIO tagging: B-PER, I-PER, O
  • Label each token

Phase 2: Features

  • Word features: Current, previous, next word
  • Context: Surrounding words, position
  • Embeddings: Word + character-level

Phase 3: Model

  • CRF: Traditional, interpretable
  • BiLSTM-CRF: Better performance
  • Fine-tuned BERT: State-of-the-art

Phase 4: Training

  • CRF: Maximum likelihood, L-BFGS
  • BiLSTM-CRF: Adam, dropout 0.5
  • BERT: Learning rate 3e-5, token classification

Phase 5: Evaluation

  • Entity-level F1 (exact match)
  • Token-level F1
  • Per entity type

Phase 6: Challenges

  • OOV words: Character embeddings, subword
  • Nested entities: Multi-label, span-based
  • Ambiguity: Context, larger window

Why NER is a sequence labelling problem and not just per-token classification.

NER — named entity recognition — assigns each token a label saying whether it starts, continues, or falls outside an entity of some type. The BIO scheme spells that out: B-PER begins a person, I-PER continues one, O is outside any entity. So “Barack Obama visited Paris” becomes B-PER I-PER O B-LOC. The reason for separate B and I tags is adjacency: without them, “Barack Obama Michelle Obama” would be indistinguishable from one four-token person and two two-token people.

The crucial structural point is that the labels are not independent. I-PER cannot legally follow O or B-LOC, because a continuation must continue something of the same type. A plain per-token softmax has no way to know this and will happily emit O I-PER, an ill-formed sequence you then have to patch up in post-processing.

What the CRF layer actually does. A conditional random field on top of the encoder adds a learned transition matrix , where scores moving from label to label . The score of a whole label sequence is the sum of per-token emission scores from the encoder plus the transition scores between consecutive labels, and training maximizes the log-probability of the true sequence normalized over all sequences — computed efficiently with the forward algorithm, since enumerating sequences is impossible. At inference, Viterbi decoding finds the highest-scoring valid sequence in by dynamic programming. The net effect is that impossible transitions learn large negative scores and the model stops emitting malformed output.

Worth knowing the modern nuance: with a strong pretrained encoder, the CRF layer’s benefit shrinks considerably, because the Transformer’s context already captures most of the constraint. Many production systems now use plain token classification plus a cheap post-processing pass to repair invalid transitions. So “always add a CRF” is out of date; “add a CRF when the tag set is large and structured, or the encoder is weak” is current.

The subword alignment problem, which is the actual implementation pitfall. BERT-family tokenizers split words into subwords, so “Washington” might become Wash ##ing ##ton — three tokens for one word-level label. The standard convention is to label the first subword of each word and set the rest to , the index PyTorch’s cross-entropy ignores, so they contribute no loss. At prediction time you take the first subword’s label as the word’s label. Getting this wrong is the single most common source of a NER model that trains fine and scores terribly, and mentioning it signals implementation experience.

Why entity-level F1 is the metric, not token accuracy. Token accuracy is inflated by the O class, which is typically 85–95% of tokens — predicting O everywhere scores 90% and finds nothing. Entity-level F1 counts a prediction correct only when both the span boundaries and the type match exactly, which is what a downstream consumer actually needs. Partial matches score zero, which is strict but right: half a person’s name is not a usable extraction.

Follow-up: Nested entities — “Bank of America” contains “America” as a location. Flat BIO cannot represent overlap at all. The standard alternatives are span-based classification (enumerate candidate spans up to some length and classify each independently, which naturally allows overlap) or a machine-reading-comprehension formulation where you ask one question per entity type and extract all matching spans. Both cost more compute; span enumeration is in sentence length.

Why the interviewer asks this. NER is the standard example of structured prediction, so the question is really “do you understand that the outputs are dependent on each other?”

Saying it out loud. NER is sequence labelling — you tag every token with BIO tags like B-PER, I-PER, O — and the key point is that the labels aren’t independent. I-PER can’t follow O, so you either add a CRF layer that learns transition scores and decodes with Viterbi, or, with a strong pretrained encoder, use plain token classification and repair invalid transitions in post-processing, which is what a lot of production systems do now. The implementation detail that trips people up is subword alignment: the tokenizer splits words into pieces, so you label the first subword of each word and mask the rest with minus one hundred so they don’t contribute loss. And I’d evaluate with entity-level F1, exact span and type match — token accuracy is useless because ninety percent of tokens are O.


Q73: What’s the standard procedure for question answering?

In 30 seconds. “For extractive QA you feed [CLS] question [SEP] context [SEP] through an encoder and put two tiny heads on top: one vector scoring each token as a possible answer start, one as a possible end. Train with cross-entropy against the true indices. At inference you don’t take the best start and best end separately — you take the top twenty of each, form valid pairs, and pick the highest product. Unanswerable questions point the span at [CLS]; long passages use overlapping sliding windows.”

The short version.

PhaseWhat happens
DataSQuAD-style context + question → answer span (start/end indices)
Modelencoder + two vectors ; logits are and
Trainingstart loss + end loss, lr 3e-5, batch 16–32, 2–3 epochs
Long contextssliding window with overlap, or paragraph ranking first
EvaluationEM (strict) and F1 (token overlap); the gap between them is informative
Productionretrieval for open-domain, re-ranking, ensembling

Why it works.

Extractive QA turns “what is the answer?” into “where is the answer?” — a question with only possible responses instead of infinitely many. That constraint is the whole value proposition: the model cannot hallucinate, because it can only point. You trade expressiveness for a grounding guarantee, and for anything a human has to audit, that trade is usually right.

The math, and what it buys you.

Answer:

Phase 1: Data Format

  • SQuAD: Context + Question → Answer span
  • Extractive: Start/end positions

Phase 2: Model

  • BERT-based: Standard approach
    • Input: [CLS] question [SEP] context [SEP]
    • Two heads: Start position, End position
    • Fine-tune BERT

Phase 3: Training

  • Load pre-trained BERT
  • Add QA head (start/end logits)
  • Loss: Start loss + End loss
  • Learning rate: 3e-5, batch 16-32, 2-3 epochs

Phase 4: Long Contexts

  • Sliding window
  • Hierarchical (paragraph ranking)
  • Long-context models

Phase 5: Evaluation

  • EM (Exact Match)
  • F1 (token overlap)
  • Per question type

Phase 6: Production

  • Retrieval for open-domain
  • Re-ranking
  • Ensemble models

How the span-prediction head actually works.

This is the mechanical part worth walking through, because “two heads: start and end” is where the answer above stops and the follow-up begins.

Feed [CLS] question [SEP] context [SEP] through the encoder to get one vector per token. Learn two vectors, and , each of size . The start logit for token is the dot product , and the end logit is . Softmax each over the sequence to get and , and train with cross-entropy against the true start and end indices, averaging the two losses. That is the entire QA head — two vectors, roughly parameters on top of the encoder.

At inference you want the best span, not the best start and the best end independently, since the argmax start and argmax end can be inconsistent (end before start, or a 400-token span). The standard decode: take the top start candidates and the top end candidates (typically ), form all pairs, discard any with or max answer length or with either index in the question rather than the context, and pick the surviving pair with the highest . Then map token indices back to character offsets in the original text — the tokenizer’s offset mapping is what makes this possible, and getting it wrong yields answers that are correct but sliced one character short.

Handling unanswerable questions. SQuAD 2.0 added questions with no answer in the passage, which is closer to reality. The standard trick is to let the span point at the [CLS] token to mean “no answer.” At inference you compare the best real span’s score against the null score , and abstain when the null score wins by more than a threshold tuned on the validation set. That threshold is a real dial: it trades false answers against false abstentions, and where you set it is a product decision.

The sliding window, in detail. For a context longer than the model’s limit, split it into overlapping windows — a common setting is a 384-token stride with 128 tokens of overlap — run each window with the same question prepended, and take the highest-scoring span across all windows. The overlap exists so that an answer straddling a window boundary appears whole in at least one window. Scores across windows are comparable because they come from the same softmax-normalized head, though they are only loosely calibrated, which is one reason very long contexts favour a retrieve-then-read pipeline over brute-force windowing.

Extractive versus generative, which is the 2026 framing. Everything above is extractive QA: the answer must be a contiguous substring of the passage. That is a genuine feature — the answer is guaranteed grounded, it cannot hallucinate, and it comes with exact character offsets for highlighting. Generative QA with an LLM handles multi-span answers, questions requiring synthesis across sentences, and reformulation into natural phrasing, at the cost of that grounding guarantee. Modern production systems mostly generate, then apply the verification machinery from Q60 to recover some of what extraction gave for free. Say which one you mean; interviewers notice when a candidate conflates them.

Why EM and F1 both. Exact match is a strict string equality after normalization (lowercase, strip articles and punctuation). F1 is computed over the bag of tokens shared between prediction and gold, so “in 1969” against “1969” scores EM 0 but F1 0.67. Reporting both tells you whether errors are boundary sloppiness or genuine misunderstanding, and the gap between them is itself informative.

Follow-up: Why not just fine-tune the model to output the answer text directly? You can, and generative models do. But extractive spans give you free provenance — you know exactly where the answer came from — which matters enormously in any setting where a human has to check the work.

Saying it out loud. For extractive QA you feed CLS, question, SEP, context, SEP through an encoder and put two tiny heads on top: one vector that scores each token as a possible answer start, one that scores it as a possible end. Softmax each over the sequence, train with cross-entropy against the true indices. At inference you don’t just take the best start and best end separately, because they can be inconsistent — you take the top twenty of each, form valid pairs where end comes after start and the span isn’t too long, and pick the highest product of probabilities. For unanswerable questions you let the span point at the CLS token and compare that null score against the best real span. And for long passages you use overlapping sliding windows so an answer straddling a boundary shows up intact in at least one.


Q74: How do you build a machine translation system?

In 30 seconds. “Parallel data, subword tokenization, an encoder-decoder Transformer, and beam search decoding. The part I’d emphasize is tokenization — word-level vocabularies drown in unknowns for morphologically rich languages, character-level makes sequences far too long, so BPE or SentencePiece keeps frequent words whole and splits rare ones into reusable pieces, usually with a shared source-target vocabulary so names and numbers copy across for free. And I’d report sacreBLEU for comparability but lean on a learned metric like COMET.”

The short version.

PhaseChoice
Dataparallel corpus, millions of pairs, domain-matched
Preprocessingsentence segmentation, BPE / SentencePiece, shared vocab
Modelencoder-decoder Transformer, multi-head attention
Traininglr 1e-4 with warmup; pretrain then fine-tune
Decodingbeam search, width 4–5, length penalty
EvaluationsacreBLEU, METEOR, COMET, human
Low-resourcemultilingual transfer + back-translation

Why it works.

Translation is the cleanest sequence-to-sequence problem: the encoder builds a meaning representation free of the source language’s surface form, and the decoder re-renders it in the target language’s surface form. Everything hard about the task sits at the two ends — how you break text into units the model can generalize over (tokenization), and how you search the space of outputs (decoding). The middle, the Transformer, is the part you mostly get off the shelf.

The math, and what it buys you.

Answer:

Phase 1: Data

  • Parallel corpus (millions of pairs)
  • High-quality translations
  • Domain match if possible

Phase 2: Preprocessing

  • Sentence segmentation
  • Subword tokenization (BPE, SentencePiece)
  • Handle rare words

Phase 3: Model

  • Transformer: State-of-the-art
  • Encoder-Decoder architecture
  • Multi-head attention

Phase 4: Training

  • Pre-train on large corpus (optional)
  • Fine-tune on translation data
  • Learning rate: 1e-4, warmup
  • Decoding: Beam search with length penalty

Phase 5: Evaluation

  • BLEU score (primary)
  • METEOR, human evaluation

Phase 6: Production

  • Multilingual models
  • Transfer learning for low-resource
  • Back-translation for data augmentation

Why subword tokenization is the load-bearing choice.

Of everything in that phase list, subword tokenization is the one that most determines whether a translation system works, so it is worth being able to explain rather than name.

Word-level vocabularies fail on translation for a specific reason: morphologically rich languages (Finnish, Turkish, German compounds) generate effectively unbounded vocabularies, so any fixed word list produces a flood of unknown tokens, and an unknown token in the source is information the decoder can never recover. Character-level avoids that but makes sequences four to five times longer, which given attention’s quadratic cost (Q89) is expensive, and it forces the model to relearn word structure from scratch.

Byte Pair Encoding splits the difference. Start with a vocabulary of individual characters. Count all adjacent symbol pairs in the corpus, merge the most frequent pair into a new symbol, and record the merge. Repeat until the vocabulary reaches the target size, typically 32K per language or 32K–64K shared. The learned result is that frequent words survive as single tokens while rare words decompose into meaningful pieces — “unhappiness” into “un”, “happi”, “ness” — so the model can handle a word it has never seen by composing parts it has. SentencePiece does the same thing but treats the input as a raw byte or Unicode stream including spaces (encoded as ), so it needs no language-specific pre-tokenizer and detokenizes losslessly — which matters for languages like Japanese and Thai that do not delimit words with spaces at all. Byte-level BPE goes further and guarantees no token is ever truly unknown, since every input is representable as bytes.

For translation specifically, a shared source-target vocabulary is standard, because it lets the model copy names, numbers and technical terms across languages directly and enables tying the embedding matrices.

Beam search, since “beam search with length penalty” hides the mechanics. Greedy decoding picks the highest-probability token at each step and can be trapped by a locally attractive choice that dooms the rest of the sentence. Beam search keeps the highest-scoring partial sequences (beam width 4–5 is standard for translation), extends each by every possible next token, and re-prunes to . Because sequence log-probability is a sum of negative numbers, longer sequences score worse automatically, so beam search left alone produces truncated output — hence a length penalty dividing the score by with around 0.6, the Google NMT formulation. Worth adding that beam search is standard for translation and not for open-ended generation, where it produces bland, repetitive text: translation has a roughly correct answer, so searching for the highest-probability output is appropriate, while open-ended text has many good answers and the highest-probability one is generic.

On BLEU, with the caveat. BLEU is a modified n-gram precision against one or more references, with a brevity penalty to stop the model gaming precision by emitting three confident words. It correlates with human judgement well enough for tracking a single system over time and badly enough that cross-system comparisons are unreliable, especially between systems of different types — and it is sensitive to tokenization, which is why sacreBLEU (which fixes the tokenization) is the reportable form. COMET and other learned neural metrics correlate substantially better with human judgement and are the current default for serious evaluation. Metric practice here moves; check what the current WMT findings recommend before committing to one.

Follow-up: What do you do for a low-resource language pair with 50K sentence pairs? Do not train from scratch. Start from a large pretrained multilingual model and fine-tune, so the language pair benefits from transfer across the other hundred languages. Then augment with back-translation — train a reverse-direction model, use it to translate abundant monolingual target-language text into synthetic source text, and add those pairs to training. It works because the synthetic noise sits on the source side where the encoder is more robust, while the target side stays fluent human text.

Why the interviewer asks this. Machine translation is the original sequence-to-sequence task, so it is a natural vehicle for probing tokenization, decoding and evaluation all at once.

Saying it out loud. The pieces are parallel data, subword tokenization, an encoder-decoder Transformer, and beam search decoding. The part I’d emphasize is tokenization — word-level vocabularies drown in unknowns for morphologically rich languages, character-level makes sequences far too long, so you use BPE or SentencePiece, which keeps frequent words whole and splits rare ones into reusable pieces. Usually a shared source-target vocabulary, so names and numbers copy across for free. For decoding, beam search with a length penalty, because without the penalty the sum of log-probs favours short output and you get truncated sentences. And I’d report sacreBLEU for comparability but lean on a learned metric like COMET, because BLEU correlates with human judgement well enough to track one system over time and poorly enough to mislead across systems.


Q75: What’s the standard approach for text summarization?

In 30 seconds. “Two families. Extractive picks sentences out of the source — TextRank builds a sentence-similarity graph and runs PageRank on it — and the nice property is that it physically cannot hallucinate, because every word came from the source. Abstractive generates new text; it reads much better and compresses across sentences, but it can invent facts. So the choice is really a risk decision, not a technique decision. And I’d report ROUGE for continuity but not trust it — it’s blind to factuality.”

The short version.

ExtractiveAbstractive
Methodscore and select sentences (TextRank, BERT scoring)fine-tuned BART/T5 or an LLM
Outputverbatim source sentencesnew text
Hallucinationstructurally impossiblepossible and common
Fluencychoppy, disjointnatural, compressed
Length controlexact (pick sentences)approximate
Training datanone needed for TextRankparallel summaries

Evaluation: ROUGE-1/2/L as the convention, plus a factual-consistency check and human judgement. Challenges: long documents, factual consistency, repetition.

Why it works.

Summarization is compression with a fidelity constraint, and the two families make opposite bets about which half matters. Extractive protects fidelity absolutely and accepts poor compression; abstractive maximizes compression and puts fidelity at risk. Once you frame it that way, the choice follows from what a wrong summary would cost in your domain.

The math, and what it buys you.

Answer:

Two Types:

Extractive:

  1. Score sentences (position, TF-IDF, similarity)
  2. Select top-K sentences
  3. Order by original position
  4. Methods: TextRank, BERT-based scoring

Abstractive:

  1. Model: Fine-tuned BART/T5
  2. Training: Encoder-Decoder, max source 1024, target 128
  3. Generation: Beam search, length penalty, repetition penalty
  4. Post-processing: Remove repetition, fix grammar

Evaluation:

  • ROUGE-1/2/L (primary)
  • BLEU, human evaluation

Challenges:

  • Long documents: Hierarchical encoding
  • Factual consistency: Fact checking
  • Repetition: Repetition penalty

Extractive versus abstractive, and why the choice is a risk decision.

The real distinction is not technique but guarantee. Extractive summarization copies sentences verbatim, so it cannot state a fact the source does not contain — factual consistency is structural, not hoped for. Abstractive summarization generates new text, so it reads far better and can compress across sentences, but it can and does invent. In regulated or high-stakes settings that guarantee is often worth more than the fluency, and saying so shows you are thinking about deployment rather than benchmarks.

How TextRank works, since it is named without explanation. Build a graph with one node per sentence and edge weights equal to the similarity between sentence pairs — originally token overlap normalized by length, now more often embedding cosine. Run PageRank over that graph: each sentence’s score is a damped sum of its neighbours’ scores weighted by edge strength, iterated to convergence. Sentences that are similar to many other well-connected sentences score highest, which operationalizes “central to what this document is about.” It needs no training data at all, which makes it a genuinely useful baseline. Follow it with a redundancy filter — maximal marginal relevance, which greedily picks the next sentence maximizing relevance minus times maximum similarity to what you have already selected — or your summary will be three paraphrases of the same central idea.

Factual consistency, since “fact checking” is listed without a method. The measurable approaches are worth naming. Entailment-based: split the summary into claims and run an NLI model with the source as premise, scoring the fraction entailed — the same machinery as RAG faithfulness in Q60. QA-based (QAGS/FEISQA family): generate questions from the summary, answer them against both the summary and the source, and compare the answers; disagreement localizes the hallucination to a specific fact. Both correlate with human judgement far better than ROUGE, which is essentially blind to factuality — a summary can hit high ROUGE while swapping a name or negating a claim, because the n-grams still overlap.

Handling long documents. Three approaches with different tradeoffs. Hierarchical: summarize each section, then summarize the summaries — cheap and parallel, but cross-section connections are lost at the first level. Refine / rolling: carry a running summary and update it chunk by chunk — preserves narrative order, but errors compound and later chunks get disproportionate influence. Long-context model in one pass — simplest and now often feasible, but subject to the lost-in-the-middle position effect from Q59, so material buried in the middle of a very long document is systematically under-represented in the summary. Knowing that failure mode is more useful than knowing the three names.

On evaluation. ROUGE-1, ROUGE-2 and ROUGE-L measure unigram, bigram, and longest-common-subsequence overlap with a reference summary. They are recall-oriented and they reward copying the reference’s wording, which means they systematically favour extractive systems and penalize good paraphrase. Report them for continuity with the literature, but pair them with a factual-consistency score and a small human or LLM-judged relevance rating, because ROUGE alone will happily rank a fluent, wrong summary above a clumsy, correct one.

Follow-up: How do you control summary length? For abstractive models, three levers: a max_length / min_length constraint at decode time, which is blunt and can cut mid-sentence; length control in the prompt or as a control token seen during training, which is softer and more natural; and a length penalty in beam search, which biases rather than enforces. For extractive, it is just how many sentences you select, which is the one case where the control is exact.

Saying it out loud. Two families. Extractive picks sentences out of the source — something like TextRank, which builds a sentence similarity graph and runs PageRank on it to find the central sentences, then a redundancy filter so you don’t return three paraphrases of the same point. The nice property is that it physically can’t hallucinate, because every word came from the source. Abstractive generates new text with a fine-tuned encoder-decoder or an LLM; it reads much better and can compress across sentences, but it can invent facts. So the choice is really a risk decision. On evaluation I’d report ROUGE for continuity but not trust it — it’s blind to factuality, so a summary that swaps a name still scores well. I’d add an entailment-based consistency check and a small human-rated set.


See 36_nlp_basics/nlp_problems_detailed.md for complete procedures for all NLP problems!


Foundation Models: Evolution from BERT to GPT-4

Q76: How did we evolve from BERT to modern foundation models like GPT-4?

In 30 seconds. “The single move that mattered was reframing every task as next-token prediction. BERT was bidirectional and great at understanding, but you needed a new head and a fine-tuning run per task. GPT-2 showed that if you write the task into the input as text, one model does translation, summarization and QA with no task-specific training. Once that holds, scale is the lever, because improving one objective improves everything at once. Everything after — few-shot prompting, RLHF, multimodality, inference-time reasoning — refines that one move.”

The short version.

PhaseModelWhat it added
2018BERTbidirectional encoder, pretrain + fine-tune per task
2019GPT-2decoder-only generation, zero-shot task framing
2020GPT-3175B params, in-context few-shot learning, scaling laws
2022InstructGPTRLHF, instruction following, alignment
2022ChatGPTconversational multi-turn interface
2023GPT-4multimodal, better reasoning, 8K → 128K context

Five paradigm shifts: task-specific → general; fine-tuning → prompting; understanding → generation; supervised → self-supervised; capability → alignment.

Why it works.

A decoder gets a supervised target at every position; BERT’s masked LM only supervises the 15% it masked. So per token of data, the decoder extracts several times more signal — and generation subsumes understanding, since you can classify by generating a label, while the reverse does not hold. What was lost is real too: causal attention sees only left context, which is why encoder models are still the right tool for embeddings, retrieval and re-ranking. “BERT was superseded” is a headline; “BERT moved to a different part of the stack” is accurate.

The math, and what it buys you.

Answer:

Phase 1: BERT (2018)

  • Bidirectional: Reads text both directions
  • Encoder-only: Good for understanding
  • Pre-training + Fine-tuning: Train on large corpus, fine-tune on tasks
  • Limitation: Can’t generate text, needs fine-tuning per task

Phase 2: GPT-2 (2019)

  • Generative: Can generate coherent text
  • Decoder-only: Autoregressive (left-to-right)
  • Zero-shot: No fine-tuning for some tasks
  • Limitation: Unidirectional, limited context

Phase 3: GPT-3 (2020)

  • Massive scale: 175B parameters
  • In-context learning: Few-shot without gradient updates
  • Scaling laws: Performance ∝ (Size)^α × (Data)^β
  • Emergent abilities: Arithmetic, code, reasoning at scale
  • Impact: Proved scaling works, foundation for modern LLMs

Phase 4: InstructGPT (2021)

  • RLHF: Reinforcement Learning from Human Feedback
  • Alignment: Align model with human preferences
  • Instruction following: Better at following instructions
  • Impact: Foundation for ChatGPT, RLHF becomes standard

Phase 5: ChatGPT (2022)

  • Conversational: Natural dialogue interface
  • RLHF: Aligned with human preferences
  • Multi-turn: Maintains context
  • Impact: Viral adoption, paradigm shift to assistants

Phase 6: GPT-4 (2023)

  • Multimodal: Text + images
  • Better reasoning: Improved logical reasoning
  • Large context: 8K → 32K → 128K tokens
  • State-of-the-art: Best performance on many tasks

Key Paradigm Shifts:

  1. Task-specific → General (single model for many tasks)
  2. Fine-tuning → Prompting (in-context learning)
  3. Understanding → Generation (decoder architectures)
  4. Supervised → Self-supervised (pre-training)
  5. Capability → Alignment (RLHF, safety)

A date correction and a currency note.

Two things to fix in the timeline above. InstructGPT is dated 2021; the models and the paper both landed in early 2022 — OpenAI released the InstructGPT models in January 2022 and the paper (Training language models to follow instructions with human feedback) in March 2022. The underlying human-preference work goes back to 2017–2020, which is probably where the 2021 came from, but the milestone itself belongs in 2022.

Second, the timeline stops at GPT-4 in 2023. Since then the field has moved through several further phases that any 2026 interviewer will expect you to at least gesture at: instruction-tuned open-weight model families becoming genuinely competitive; mixture-of-experts architectures becoming standard for frontier-scale models, where only a fraction of parameters activate per token so capacity grows without proportional inference cost; natively multimodal training (audio, image and video in the same model from the start rather than a vision encoder bolted on); context windows moving from 128K to the million-token range; and explicit reasoning models trained to spend extra compute at inference time producing intermediate reasoning before answering — which shifted the field’s attention from training-time scaling to inference-time scaling. Everything in this paragraph is time-sensitive and specific model names date within months; treat it as a direction of travel rather than a fact sheet, and verify current specifics.

The mechanism behind the biggest jump, stated as one idea. If you have to compress the whole evolution into a single sentence, it is this: every NLP task was reformulated as text prediction. BERT still needed a new output head and a new fine-tuning run per task. GPT-2’s contribution was demonstrating that translation, summarization and question answering all fit inside “predict the next token” if you simply write the task into the input. Once that reframing holds, scale becomes the lever, because improving one objective improves every task at once, and you no longer need labelled data per task. Everything after — in-context learning, instruction tuning, RLHF, tool use — is a refinement of that single move.

Why decoder-only won, mechanically. Three reasons that are worth separating. Training efficiency: a decoder gets a prediction target at every one of positions, whereas BERT’s masked language modelling only supervises the 15% of positions that were masked, so per token of data the decoder extracts several times more signal. Generality: generation subsumes understanding — you can classify by generating a label — but the reverse does not hold. And the pretrain/fine-tune mismatch: BERT sees [MASK] tokens during pretraining that never occur at fine-tuning time, whereas a decoder’s training and inference objectives are identical.

What was actually lost. Be honest about the tradeoff, because it makes the answer credible. Causal attention means each token only sees its left context, which is strictly less information than BERT’s bidirectional view for pure encoding tasks. That is why encoder models remain the right tool for embeddings, retrieval, re-ranking and high-throughput classification, and why BERT-family models are still running in enormous volume in production in 2026 — they are smaller, faster, cheaper, and better at producing a single vector representing a whole text. “BERT was superseded” is a headline; “BERT moved to a different part of the stack” is the accurate version.

Follow-up: What ended the pure-scaling era? Chinchilla (2022) showed the big models of the day were badly undertrained for their size — the compute-optimal ratio is roughly 20 training tokens per parameter, so GPT-3’s 175B parameters on 300B tokens was far off optimal. That redirected effort from parameter count to data quality and quantity. Since then the binding constraints have shifted again toward high-quality data availability and toward inference cost, which is what motivates mixture-of-experts, distillation, and spending compute at inference time instead of training time.

Why the interviewer asks this. They want to know whether you can tell a causal story with mechanisms, or only recite a sequence of model names and parameter counts.

Saying it out loud. The single move that mattered was reframing every task as next-token prediction. BERT was bidirectional and great at understanding, but you needed a new head and a fine-tuning run per task. GPT-2 showed that if you just write the task into the input as text, one model does translation, summarization, and QA without task-specific training. Once that holds, scale is the lever, because improving one objective improves everything at once. GPT-3 proved few-shot prompting works at scale, InstructGPT and RLHF in 2022 made models actually follow instructions rather than just continue text, and GPT-4 added multimodality and much better reasoning. Since then the interesting shift has been from scaling training compute to scaling inference compute — models that think longer before answering.


Q77: What are scaling laws? How do they explain the success of large models?

In 30 seconds. “Scaling laws say test loss falls as a power law in model size, data and compute — straight lines on a log-log plot over many orders of magnitude. The exponent is small, about 0.076 for parameters, so ten times the model buys roughly a sixteen percent loss reduction: it works, but it’s expensive per unit. The important refinement is Chinchilla — grow parameters and data together, roughly twenty tokens per parameter — by which standard GPT-3 was badly undertrained. The practical value is fitting the curve on cheap small runs and extrapolating.”

The short version.

  • Form: loss goes down, as a power law, not performance up.
  • Exponents: (params), (data), (compute).
  • Chinchilla rule: compute-optimal is about 20 tokens per parameter.
  • Predictability: fit on small runs, extrapolate to forecast a run you haven’t paid for.
  • Limits: loss cannot fall below the irreducible entropy of text; high-quality data is finite.

Why it works.

Nobody has a complete theory. The leading intuitions are that language itself has power-law structure (Zipf) and that bigger models resolve finer-grained sub-distributions of the data. The honest position is that this is a robust empirical regularity, not a derived law — which is exactly why the interesting question is where it bends, not how far it extends.

The math, and what it buys you.

Answer:

Neural Scaling Laws:

Performance = f(Model Size, Data Size, Compute)

Performance improves predictably with:
- Model size (parameters)
- Training data size
- Compute budget

Key Findings:

1. Power Law Relationship:

  • Performance ∝ (Model Size)^α
  • α ≈ 0.076 (diminishing returns but still improves)
  • Larger models = better performance

2. Data Scaling:

  • Larger models need more data
  • Optimal data size ∝ Model size
  • More data = better performance

3. Compute Scaling:

  • More compute = better performance
  • Optimal allocation: Scale model, data, compute together

4. Predictable Improvements:

  • Can predict performance before training
  • Helps with planning and resource allocation

Implications:

  • Bigger is better: Larger models perform better
  • Data matters: Need more data for larger models
  • Massive compute: Requires huge compute budgets
  • Predictable: Can estimate performance

Why It Matters:

  • Explains why GPT-3 succeeded
  • Guides model development
  • Shows path to better models
  • Justifies investment in scale

What the law actually says, with the exponents made precise.

The formula in the answer is loosely stated in a way that inverts the meaning, so it is worth writing carefully. Scaling laws are about loss, and loss goes down as a power law:

where is non-embedding parameter count and is cross-entropy loss in nats per token (Kaplan et al., 2020). So “performance size” should read “loss size.” The exponent being small is the important part: because is tiny, you need roughly a increase in parameters to cut loss by a factor of , i.e. about 16%. Scaling works, and it is brutally expensive per unit of improvement. Analogous power laws hold in dataset size with and in compute with , and the three are only independent while the other two are not the bottleneck.

Chinchilla, which changed the practical conclusion and is missing above. Kaplan’s original analysis suggested that given more compute you should mostly grow the model. Hoffmann et al. (2022) redid the experiment more carefully and found and should scale roughly equally: compute-optimal training uses about 20 tokens per parameter. By that rule GPT-3 (175B parameters, ~300B tokens, under 2 tokens per parameter) was drastically undertrained — a 70B model trained on 1.4T tokens (Chinchilla) beat it while being 2.5× smaller and far cheaper to serve. This is the single most important correction to the naive “bigger is better” reading and it belongs in any answer to this question.

Note the further practical twist: compute-optimal is about training cost only. If you will serve a model to millions of users, inference cost dominates total cost, so it is rational to train a smaller model on far more tokens than Chinchilla-optimal — well past the point of diminishing training returns — because you pay the training bill once and the inference bill forever. That is why recent small models are trained on token counts hundreds of times their parameter count.

Why the curve stays straight, and where it must bend. There is no complete theory. The leading intuitions are that natural language itself has power-law structure (Zipf), and that larger models can resolve finer-grained sub-distributions of the data. The honest position is that scaling laws are a robust empirical regularity across many orders of magnitude, not a derived law. And they must break somewhere: the loss cannot fall below the irreducible entropy of the text — genuinely unpredictable content, so the fitted forms include an additive constant — and high-quality human text is a finite resource, which is the constraint now driving synthetic data, multi-epoch training, and multimodal data.

Why they matter operationally. The practical payoff is not philosophical, it is budgetary: fit the curve on small runs — a handful of models spanning two or three orders of magnitude of compute — and extrapolate to predict the loss of a run you have not yet paid for. That is how a training run costing tens of millions of dollars gets approved by people who need a forecast rather than a hope. It also lets you choose hyperparameters (batch size, learning rate) at small scale and transfer them.

Follow-up: Does lower loss actually mean a better product? Only loosely. Loss is next-token cross-entropy averaged over a pretraining distribution, and downstream benchmark scores are a noisy, sometimes discontinuous function of it (Q80). A model can improve on loss while getting worse on the thing you care about, and post-training — instruction tuning, preference optimization — moves downstream quality substantially without moving pretraining loss at all.

Why the interviewer asks this. They are checking whether “scale it up” is a slogan for you or a quantitative statement you can reason about, and the Chinchilla correction is the tell.

Saying it out loud. Scaling laws say that test loss falls as a power law in model size, data size, and compute — smooth, straight lines on a log-log plot over many orders of magnitude. The exponent is small, about 0.076 for parameters, which means ten times the model gets you roughly a sixteen percent loss reduction; it works, but it’s expensive per unit of improvement. The important refinement is Chinchilla — the original analysis said grow the model, and Chinchilla showed you should grow parameters and data together, roughly twenty tokens per parameter. By that standard GPT-3 was badly undertrained. The practical value is that you fit the curve on small cheap runs and extrapolate, which is how you justify a nine-figure training run before spending the money.


Q78: What is in-context learning? How does it differ from fine-tuning?

In 30 seconds. “In-context learning is putting examples in the prompt and having the model adapt with no weight updates at all — same frozen model for every task. Fine-tuning changes the weights. The mechanism is more interesting than it looks: replacing the labels in your few-shot examples with random ones barely hurts, which means the examples mostly convey format and label space, not the mapping. So it’s closer to locating a capability the model already has than to learning one. And you re-pay for those tokens on every single call.”

The short version.

AspectIn-context learningFine-tuningLoRA (the middle ground)
Weight updatesnoneall parameters<1% of parameters
Data neededa handful in the promptlabelled datasetlabelled dataset
Models per taskone sharedone eachone base + small adapters
Switching tasksedit the promptretrainswap an adapter
Per-query costpays for demos every callcheapcheap
Peak qualitygoodbestnear-best

Why it works.

The best current mechanistic evidence is induction heads: pairs of attention heads that spot a repeated pattern earlier in the context and copy whatever followed it last time. They appear abruptly during training, and their appearance coincides with a visible jump in in-context learning ability. That is pattern completion, not gradient descent — which is exactly why format matters more than label correctness.

The math, and what it buys you.

Answer:

In-Context Learning:

  • Model learns from examples in the prompt
  • No gradient updates
  • No weight changes
  • Same model for all tasks

Types:

Zero-shot:

"Translate to French: hello →"
Model generates: "bonjour"

One-shot:

"Translate to French: hello → bonjour, cat →"
Model generates: "chat"

Few-shot:

"Translate to French: hello → bonjour, cat → chat, dog →"
Model generates: "chien"

Fine-tuning:

  • Update model weights
  • Task-specific model
  • Requires labeled data
  • Gradient updates
  • Different model per task

Key Differences:

AspectIn-Context LearningFine-tuning
Weight UpdatesNoYes
DataExamples in promptLabeled dataset
ModelSame for all tasksDifferent per task
FlexibilityEasy to changeNeed retraining
PerformanceGood for many tasksBest for specific task

Why In-Context Learning Works:

  • Large models have seen similar patterns
  • Can generalize from examples
  • Emergent ability at scale
  • Flexible and efficient

When to Use:

  • In-context: Quick prototyping, many tasks, no labeled data
  • Fine-tuning: Best performance, specific task, have labeled data

Why it works, beyond “the model has seen similar patterns.”

That explanation is not wrong but it is not a mechanism, and a good interviewer will push. Three more substantive accounts, worth knowing because none is fully settled:

Task location rather than task learning. On this view the model already contains many capabilities from pretraining, and the prompt’s examples act as a selector that identifies which distribution to condition on, not as training data. The strongest evidence is the striking finding that replacing the labels in few-shot examples with random labels often barely hurts performance — which is impossible if the model were learning the input-output mapping from them. What the examples mainly convey is the input distribution, the label space, and the output format.

Implicit gradient descent. Several analyses show that a Transformer’s forward pass over in-context examples can implement something formally equivalent to a gradient-descent step on a linear model, with the attention mechanism playing the role of the update. Toy models trained on synthetic regression tasks do learn algorithms of this shape. Whether large trained-on-text models actually do this is contested.

Induction heads. A concrete, mechanistically-verified circuit: pairs of attention heads that detect a repeated pattern earlier in the context and copy what followed it. They form abruptly during training, and their formation coincides with a visible bump in in-context learning ability. This is the most solid mechanistic evidence available and is the answer to give if pressed for something specific.

The honest summary: in-context learning is a real, measurable phenomenon whose mechanism is partly understood and actively researched. Saying that is stronger than asserting a single explanation.

Practical facts that make an answer credible.

Ordering matters — the same examples in a different order can move accuracy by tens of points on some tasks, with a bias toward the label of the final example. Format matters more than correctness, per the random-label result. The gains from more examples saturate quickly, typically by 8 to 32, and long-context models have not changed that as much as expected. And in-context learning re-processes the examples on every call, so a system serving a million queries a day pays for the demonstrations a million times — which is precisely the economic argument for eventually distilling into a fine-tuned model.

The middle ground the table omits. In 2026 the choice is not binary. Parameter-efficient fine-tuning, above all LoRA — freezing the base weights and training a pair of low-rank matrices whose product is added to selected weight matrices — trains well under 1% of the parameters, produces adapters of a few megabytes, and lets you serve many task-specific adapters against one shared base model. That collapses the “different model per task” cost that makes the fine-tuning column look expensive. There is also prompt caching, where a provider caches the encoded prefix so repeated demonstrations cost a fraction of full input price, which pushes in the other direction. The real decision is a cost-per-query and quality curve, not a philosophy.

Follow-up: When should you definitely fine-tune? When you need a specific output format reliably every time; when the task needs more demonstrations than fit comfortably in context; when per-query latency and cost matter at volume and you want to stop paying for a long prompt; or when you need a small model to match a large one on one narrow task, which distillation from the large model’s outputs does well.

Why the interviewer asks this. It is the fastest way to find out whether you have deployed LLMs or only used them, because everyone knows the definitions and only practitioners know the ordering effects and the cost math.

Saying it out loud. In-context learning is when you put examples in the prompt and the model adapts, with no weight updates at all — same frozen model for every task. Fine-tuning changes the weights. The mechanism is more interesting than it first looks: there’s a well-known result that replacing the labels in your few-shot examples with random ones barely hurts, which means the examples are mostly conveying the format and the label space, not teaching the mapping. So it’s closer to locating a capability the model already has than to learning. Practically, the order of examples matters more than people expect, gains saturate around eight to thirty-two examples, and you re-pay for those tokens on every single call — which is the real argument for eventually fine-tuning, ideally with LoRA so you’re training under one percent of the parameters.


Q79: Explain RLHF (Reinforcement Learning from Human Feedback). Why is it important?

In 30 seconds. “Three stages. Supervised fine-tuning on human demonstrations so the model follows instructions at all. Then a reward model: show humans two responses, ask which is better, train a model to predict that — because people are much better at comparing two answers than at writing the perfect one. Then RL against that reward with a KL penalty keeping the policy near where it started. That KL term is the crucial bit; without it the model finds text that scores enormously well and is garbage to a human.”

The short version.

StageInputOutput
1. SFThuman-written prompt/response demosa model that follows instructions
2. Reward modellingpairwise “which is better?” comparisonsa scalar scorer
3. RL (PPO) or DPOprompts + reward (or preference pairs)an aligned policy

Why it matters: alignment (behaviour ≠ capability), instruction following, refusal of harmful requests, and a usable product. Challenges: expensive human feedback, subjective preferences, reward hacking.

Why it works.

The core insight is about the labelling economy. Supervised fine-tuning needs someone to author an ideal answer, which is slow, expensive, and often impossible when many answers are equally good. Comparisons are cheap, fast, and far more consistent between annotators. RLHF exists to convert that cheap comparison signal into a differentiable training signal — which is precisely what the reward model is for.

The math, and what it buys you.

Answer:

RLHF aligns language models with human preferences using reinforcement learning.

Three Steps:

Step 1: Supervised Fine-tuning (SFT)

1. Collect human-written prompts and responses
2. Fine-tune base model (GPT-3) on this data
3. Model learns to follow instructions

Step 2: Reward Modeling

1. Collect comparisons: Which response is better?
2. Train reward model to predict human preferences
3. Reward model scores: response_A > response_B

Step 3: Reinforcement Learning (PPO)

1. Generate responses from SFT model
2. Score with reward model
3. Update model to maximize reward
4. Use PPO (Proximal Policy Optimization)

Why Important:

1. Alignment:

  • Model behavior ≠ model capability
  • Need to align with human values
  • Helpful, harmless, honest

2. Better User Experience:

  • Follows instructions better
  • More helpful responses
  • Admits mistakes
  • Refuses harmful requests

3. Safety:

  • Can make models safer
  • Reduces harmful outputs
  • Better control

Impact:

  • Foundation for ChatGPT
  • Standard practice for alignment
  • Drives research in alignment
  • Better user experience

Challenges:

  • Expensive (human feedback)
  • Subjective (different preferences)
  • Can be gamed (reward hacking)
  • Ongoing research

Why reinforcement learning at all, rather than more supervised fine-tuning?

This is the question behind the question, and it has a clean answer. Supervised fine-tuning teaches by imitation: it needs a human to write an ideal response, and it can only ever pull the model toward that one response. But for most prompts there is no single ideal answer, writing good answers is slow and expensive, and — critically — humans are far better at comparing two answers than at authoring one from scratch. Preference comparisons are cheaper, more reliable, and more consistent between annotators. RLHF exists to convert that cheap comparison signal into a training signal, which requires a reward model because comparisons are not differentiable targets.

The reward model, concretely. Take the SFT model, strip the token-prediction head, and attach a scalar head that outputs one number per (prompt, response) pair. Train on pairs where a human said (winner) beats (loser), using the Bradley–Terry loss:

Reading it: push the winner’s score above the loser’s, with a sigmoid so the pressure eases once the gap is comfortable. Only score differences are meaningful — the absolute scale is arbitrary — which is why reward values across different reward models are not comparable.

The PPO objective, with the term everyone forgets. The optimized objective is

The second term — a KL penalty against the SFT model — is not a detail, it is what makes the whole thing work. The reward model is only accurate near the distribution it was trained on; push the policy far from that and it finds adversarial text that scores enormously well and is gibberish to a human. That is reward hacking, and without the KL leash it happens quickly and reliably. Sometimes the failure is subtler and therefore worse: the model discovers that long, hedged, flattering answers score highly, and you get verbosity and sycophancy rather than obvious nonsense.

DPO, which the answer above predates. Direct Preference Optimization (2023) showed that the constrained RL objective above has a closed-form optimal policy, which can be rearranged so that the reward model is expressed in terms of the policy itself. The result is that you can optimize preferences with a simple supervised-style loss on preference pairs, with no separate reward model and no RL loop:

It is dramatically simpler to implement and tune, and it is now the default for most open-weight alignment work, with a family of variants (IPO, KTO, ORPO, and online/iterative versions) addressing its limitations. Frontier labs still use online RL methods too, partly because online generation lets the model explore beyond a fixed preference dataset. Alignment methodology is one of the fastest-moving parts of the field — verify what is current before asserting a default.

Also worth naming: Constitutional AI / RLAIF, where the preference labels come from a model judging against a written set of principles instead of from humans, which removes the human-labelling bottleneck and makes the value judgements auditable as text.

What RLHF does not fix. It aligns the model to the preferences of the annotator pool you hired, which is a specific and non-neutral set of people. It optimizes for what raters approve of, which is not the same as what is true — hence the well-documented tendency toward sycophancy, since agreeing with the user is reliably rated highly. And it can degrade raw capability slightly, the “alignment tax.” These limits are the interesting part of the answer.

Follow-up: What is reward hacking, in one concrete example? The classic pattern: raters mildly prefer longer, more thorough answers, so the reward model learns length as a proxy for quality, and the policy learns to pad. You catch it by tracking response length alongside reward during training — a reward curve rising in lockstep with length is the signature — and by holding out a human evaluation that the reward model never sees.

Why the interviewer asks this. RLHF is where capability turns into a usable product, so understanding it signals that you think about deployment and not only about training loss.

Saying it out loud. RLHF is three stages. First supervised fine-tuning on human-written demonstrations so the model follows instructions at all. Then a reward model: show humans two responses, ask which is better, and train a model to predict that preference — because people are much better at comparing two answers than at writing the perfect one. Then reinforcement learning against that reward, with a KL penalty keeping the policy close to where it started. That KL term is the crucial bit — without it the model finds text that scores enormously well on the reward model and is garbage to a human, which is reward hacking. Worth adding that DPO has largely replaced the RL loop in open-weight work: it turns out you can optimize the same objective directly on preference pairs with no reward model at all.


Q80: What are emergent abilities? Give examples.

In 30 seconds. “The standard story is that some abilities don’t exist below a certain scale and then appear — chain-of-thought is the cleanest example, since asking a small model to think step by step doesn’t help and can hurt, while asking a big one helps a lot. But I’d raise the counterargument myself: a lot of these jumps are metric artefacts. Score with exact match on a five-token answer and per-token accuracy rising smoothly from 70% to 95% turns into 17% to 77% — which looks like a cliff. Switch to a continuous metric and the curve is smooth.”

The short version.

  • Claim: capabilities absent in small models appear abruptly at scale, without being trained for.
  • Standard examples: arithmetic, code generation, few-shot learning, multi-step reasoning, instruction following.
  • Best-documented example: chain-of-thought prompting, which helps only above a scale threshold.
  • Counterargument: discontinuous metrics manufacture discontinuous-looking curves.
  • What survives: for a fixed metric you care about, the usable/unusable threshold is real and not currently predictable from the loss curve.

Why it works.

The mirage argument is worth internalizing because it generalizes: any metric that multiplies per-step probabilities converts smooth improvement into an apparent cliff. It is the same shape as a relay team — if each runner independently improves slightly, the probability that all of them succeed improves nonlinearly. The model changed smoothly; the yardstick did not.

The math, and what it buys you.

Answer:

Emergent Abilities:

  • Abilities that appear only at large scale
  • Not present in smaller models
  • Unexpected capabilities
  • Emerge from scale, not explicit training

Examples:

1. Arithmetic:

  • Small models: Can’t do math
  • Large models: Can do arithmetic (not explicitly trained)
  • Example: “What is 123 × 456?” → Correct answer

2. Code Generation:

  • Small models: Can’t write code
  • Large models: Can generate working code
  • Example: “Write Python function to sort list” → Working code

3. Few-shot Learning:

  • Small models: Need many examples
  • Large models: Learn from few examples
  • Example: 1-2 examples sufficient

4. Reasoning:

  • Small models: Limited reasoning
  • Large models: Some logical reasoning
  • Example: Multi-step problem solving

5. Instruction Following:

  • Small models: Don’t follow instructions well
  • Large models: Better at following instructions
  • Example: Complex multi-step instructions

Why Important:

  • Shows scale matters
  • Unexpected capabilities
  • Hard to predict what will emerge
  • Justifies investment in scale

Implications:

  • Can’t predict all capabilities
  • Need to test large models
  • Emergent abilities are powerful
  • Safety concerns (unexpected behaviors)

The strong counterargument you should raise yourself.

Emergence is the one topic in this section where reciting the standard answer is a weaker response than complicating it. In 2023, Are Emergent Abilities of Large Language Models a Mirage? (Schaeffer et al.) argued that many reported emergent jumps are artefacts of the metric, not the model. The argument is precise and worth being able to reproduce.

Take multi-digit arithmetic scored by exact match. Exact match on a 5-token answer is roughly the joint probability of getting every token right, so if per-token accuracy improves smoothly from 0.70 to 0.95, exact match goes from to — the underlying capability moved smoothly, but a metric that multiplies per-token probabilities converts that into a curve that looks flat then explodes. Swap to a continuous metric such as token edit distance or the log-probability of the correct answer, and the same models show smooth, predictable improvement with no discontinuity at all. Discontinuous, all-or-nothing metrics manufacture discontinuous-looking capabilities.

There are secondary artefacts too: small test sets make near-zero scores indistinguishable from zero, and log-spaced model sizes make a smooth curve look like a step because you sampled it three times.

What survives the critique. Not everything. Even granting the metric argument, two things remain real and useful. First, for a fixed metric that you care about, capability really is unusable below some scale and usable above it, and that threshold is not currently predictable from the loss curve — which is an operational problem regardless of whether the underlying capability is smooth. Second, some phase-transition-like phenomena have been observed with continuous metrics and mechanistic explanations, notably the abrupt formation of induction heads during training (Q78) and grokking, where generalization appears long after memorization. So the defensible position is: loss scales smoothly and predictably; downstream capability on discrete metrics can appear abruptly, sometimes genuinely and sometimes as a measurement artefact.

Why the specific examples above need care. “Large models can do arithmetic” is a fragile claim — models are unreliable at multi-digit multiplication and their accuracy falls off sharply with digit count unless they use chain-of-thought or a calculator tool. What genuinely emerged with scale is chain-of-thought prompting itself: asking a small model to reason step by step does not help and often hurts, while asking a sufficiently large model to do so produces large gains. That is a cleaner and better-documented example than raw arithmetic. Similarly, instruction following is better described as a product of instruction tuning and RLHF than as something that emerged from scale alone.

Follow-up: Why does this matter for safety? Because it bears on predictability. If capabilities genuinely appear without warning, you cannot certify a model’s behaviour before training it, and evaluation must happen after the fact. If the apparent unpredictability is largely a metric artefact, then better-designed continuous evaluations could forecast capabilities in advance — which would be a substantially better world to be in. That is the practical stake in what looks like an academic dispute.

Why the interviewer asks this. It is a soft test for whether you read critically; a candidate who volunteers the mirage critique is signalling that they track the literature rather than the headlines.

Saying it out loud. The standard story is that some abilities just don’t exist below a certain scale and then appear — chain-of-thought reasoning is the cleanest example, since asking a small model to think step by step doesn’t help and can hurt, while asking a big one helps a lot. But I’d raise the counterargument, because it’s a good one. There’s a well-known paper arguing a lot of these jumps are metric artefacts: if you score with exact match on a five-token answer, per-token accuracy going smoothly from seventy to ninety-five percent turns into exact-match going from seventeen to seventy-seven, which looks like a cliff. Switch to a continuous metric and the curve is smooth. What survives is the operational point — for the metric you actually care about, capability can be unusable at one scale and usable at the next, and we can’t currently predict where that happens from the loss curve.


Q81: How do modern foundation models differ from BERT?

In 30 seconds. “BERT is an encoder with bidirectional attention trained by masking tokens, built for understanding and unable to generate. Modern foundation models are decoders with causal attention trained on next-token prediction, so they generate and understanding comes free through prompting — three orders of magnitude more scale, plus RLHF. But I’d push back on ‘BERT was replaced’: it moved. Every dense retriever and re-ranker in a RAG stack is an encoder. The field split — encoders for representation, decoders for generation.”

The short version.

AspectBERTModern foundation models
Architectureencoder-onlydecoder-only (causal)
Directionbidirectionalunidirectional
Objectivemasked LM (+ NSP)next-token prediction, then RLHF
Scale110M–340M params, ~3B tokens175B+ params, trillions of tokens
Usagefine-tune per taskprompt / in-context learning
Primary strengthunderstanding, representationgeneration, generality
Still best forembeddings, re-ranking, NER, high-volume classificationopen-ended tasks, reasoning, agents

Why it works.

The causal mask is the whole difference. It costs you information — a token cannot see its own future — which is exactly why encoders remain better at squeezing a whole passage into one vector. And it buys you a training target at every position plus an inference-time generation loop, which is exactly why decoders scaled further. Neither is strictly better; they trade the same quantity in opposite directions.

The math, and what it buys you.

Answer:

Architecture:

BERT:

  • Encoder-only
  • Bidirectional
  • Good for understanding
  • Can’t generate

Modern Foundation Models (GPT-4, etc.):

  • Decoder-only (or encoder-decoder)
  • Unidirectional (for generation)
  • Good for generation
  • Can do understanding (with prompting)

Training:

BERT:

  • Masked language modeling
  • Next sentence prediction
  • ~3B tokens
  • Task-specific fine-tuning

Modern:

  • Next token prediction
  • Trillions of tokens
  • RLHF for alignment
  • In-context learning

Capabilities:

BERT:

  • Understanding tasks
  • Classification, NER, QA
  • Needs fine-tuning per task
  • Task-specific models

Modern:

  • Generation + understanding
  • Many tasks with one model
  • In-context learning
  • General-purpose

Scale:

BERT:

  • 110M-340M parameters
  • Small datasets
  • Moderate compute

Modern:

  • 175B+ parameters
  • Trillions of tokens
  • Massive compute

Usage:

BERT:

  • Fine-tune for specific task
  • Different model per task
  • Requires labeled data

Modern:

  • Prompt with examples
  • Same model for all tasks
  • No labeled data needed (few-shot)

Key Differences Summary:

AspectBERTModern Foundation Models
ArchitectureEncoderDecoder
DirectionBidirectionalUnidirectional
Primary UseUnderstandingGeneration
Scale110M-340M175B+
TrainingMLM + NSPNext token + RLHF
UsageFine-tuningIn-context learning
TasksTask-specificGeneral-purpose

Correcting the framing: BERT was not replaced, it was relocated.

The comparison table is accurate but its implicit story — old versus new — is misleading, and correcting it is the most valuable thing you can add here. BERT-family encoders are running at enormous volume in production in 2026, because for a large class of jobs they are strictly the better tool.

Embeddings and retrieval. Producing one vector for a passage is exactly what a bidirectional encoder is built for, and it is what every dense retriever and re-ranker in Q57–Q70 uses. A decoder’s causal mask means early tokens cannot see later ones, which is a real handicap for summarizing a whole text into one vector; decoder-derived embedding models exist and work, but they typically need architectural surgery (removing the causal mask, or bidirectional adaptation) to get there.

High-throughput classification. Spam filtering, content moderation triage, intent routing, and toxicity scoring at millions of requests per hour. A 100M-parameter encoder runs in single-digit milliseconds on a CPU. Routing that traffic through a frontier LLM would cost orders of magnitude more per request and add latency for no accuracy gain on a task with abundant labels.

Token-level tasks. NER and other span extraction (Q72) benefit directly from bidirectional context — knowing what comes after a token genuinely helps classify it — and produce structured output that is awkward to coax reliably out of free-form generation.

The right summary is that the field bifurcated: encoders for representation, decoders for generation, with each dominating a different part of the stack.

Two technical corrections to the table.

“Unidirectional” understates modern architecture. Frontier models are decoder-only with causal attention, yes, but the table’s “Decoder” row hides a lot: rotary or ALiBi positional encodings instead of learned absolute positions, RMSNorm instead of LayerNorm, pre-normalization instead of post, SwiGLU instead of ReLU feed-forward blocks, grouped-query or multi-query attention to shrink the KV cache, and mixture-of-experts for sparse capacity. “Decoder-only Transformer” describes the family, not the design.

NSP was a mistake. BERT’s next-sentence-prediction objective was shown by RoBERTa to be useless or mildly harmful, and essentially every successor dropped it. Listing it as a defining feature of BERT is technically right and historically a footnote about what did not work.

One more axis the table misses: what you can inspect. A fine-tuned BERT is a fixed function you can evaluate exhaustively, version, and audit — you know precisely what it was trained on and its behaviour does not change unless you change it. A hosted frontier model can be updated underneath you, behaves differently with a reworded prompt, and cannot be exhaustively characterized. For regulated deployments that difference frequently decides the architecture regardless of accuracy.

Follow-up: If you had to pick one model for a new text classification task today, which? I would prompt an LLM first to get a zero-shot baseline and to generate labels cheaply, then distil into a fine-tuned encoder for serving. That gets the LLM’s quality with the encoder’s cost and latency, and it is the pattern most teams converge on once they see the inference bill.

Saying it out loud. Architecturally, BERT is an encoder with bidirectional attention trained by masking tokens, so it’s built for understanding and can’t generate. Modern foundation models are decoders with causal attention trained on next-token prediction, so they generate, and understanding comes along for free through prompting. Scale differs by three orders of magnitude, and modern models add RLHF on top. But I’d push back on the framing that BERT was replaced — it moved. Every dense retriever and re-ranker in a RAG stack is an encoder, because producing one vector for a passage is exactly what bidirectional attention is good at. And for high-volume classification, a hundred-million-parameter encoder runs in milliseconds on CPU where an LLM call costs orders of magnitude more. The field split: encoders for representation, decoders for generation.


See 38_multimodal_and_embeddings/foundation_models_evolution.md for complete evolution story!


Multimodal Integration and World Models

Q82: How do you integrate triplet data (knowledge graphs) into foundation models?

In 30 seconds. “The real question is where you want the knowledge to live. You can bake triples into the weights by verbalizing them into sentences and training on them — no retrieval at inference, but updating one fact means retraining and nothing stops the model hallucinating around it. Or you keep the graph external and retrieve the relevant subgraph at query time, which is RAG with a graph instead of a text index. That’s my default: facts update instantly, provenance is free, no training run.”

The short version.

ApproachKnowledge lives inUpdate costProvenance
Direct encoding (verbalize triples, train)weightsretrainnone
KG embeddings (TransE/TransR) aligned to LMweightsretrainnone
Structured prompting (retrieve subgraph)the graphinstant writefull
Multi-task learning (LM + triple prediction)weightsretrainnone

Pipeline: collect (Wikidata, Freebase) → clean and dedupe → convert triples to text or subgraphs → integrate. Rough guidance if training: mix around 20% triple-derived text with natural text, and treat that as an upper bound to tune.

Why it works.

A knowledge graph and a language model store knowledge with opposite properties: exact, discrete and cheap to edit versus fuzzy, distributed and expensive to edit. Integration is a bridge between the two, and every design choice here is really an answer to “which of those properties do I need for this fact?” Facts that change — prices, org charts, policies — belong in the graph. Facts that are stable and pervasive can go in the weights.

The math, and what it buys you.

Answer:

Triplet Data:

  • Format: (Subject, Relation, Object)
  • Example: (“Einstein”, “born_in”, “Germany”)
  • Represents structured knowledge

Integration Strategies:

1. Direct Encoding:

  • Convert triplet to text: “Einstein [born_in] Germany”
  • Add to training corpus
  • Model learns relationships

2. Knowledge Graph Embedding:

  • Pre-train embeddings (TransE, TransR)
  • Learn entity and relation embeddings
  • Integrate into language model

3. Structured Prompting:

"Given: (Einstein, born_in, Germany)
Question: Where was Einstein born?
Answer: Germany"

4. Multi-Task Learning:

  • Language modeling + triplet prediction
  • Joint training on all tasks

Processing Pipeline:

  1. Data collection (Wikidata, Freebase)
  2. Data cleaning (remove duplicates, validate)
  3. Format conversion (triplet → text)
  4. Integration (mixed corpus or separate objective)

Best Practice:

  • Mix 20% triplet-derived text with 80% natural text
  • Use knowledge graph embeddings for better reasoning
  • Fine-tune on domain-specific triplets

Why this is harder than it looks, and what the real tradeoff is.

A knowledge graph is a set of facts stored as (subject, relation, object) triples — structured, exact, and easy to update. A language model stores knowledge as distributed weights — fuzzy, approximate, and expensive to update. Integration means bridging two representations with opposite properties, and the central question is where the knowledge should live.

Baking it into the weights (options 1, 2 and 4 above). Verbalize triples into sentences and pretrain or fine-tune on them, or align KG embeddings with the language model’s space. The advantage is that the knowledge becomes available with no retrieval step at inference. The disadvantages are severe and worth naming: updating one fact requires retraining or some form of model editing; the model may still hallucinate around the fact because nothing enforces the constraint at generation time; and verbalized triples are stilted text that can degrade fluency if you mix in too much — which is why the “20% triple-derived text” guidance in the answer above should be treated as a rough upper bound rather than a target, and tuned by measuring both factual accuracy and general language quality, since it is easy to buy the first with the second.

Keeping it external (option 3, structured prompting) is usually the better default. Retrieve the relevant subgraph at query time and put it in the context. Facts update instantly by writing to the graph, you get provenance for free, and there is no training run. This is RAG with a graph instead of a text index, and it is where most production systems land. The retrieval step is the interesting engineering: you need entity linking to map surface strings in the query to graph nodes, then a neighbourhood expansion of one or two hops, then serialization of the subgraph into text the model can read. Two hops from a well-connected entity can pull in thousands of triples, so you need relevance filtering — usually by relation type or by embedding similarity to the query — or you blow the context window on irrelevant edges.

The technique worth naming: GraphRAG. Rather than assuming a pre-existing knowledge graph, extract entities and relations from your document corpus with an LLM, build a graph, cluster it into communities, and pre-generate a summary per community. Then a query can be answered either locally (relevant entities and their neighbourhoods) or globally (over the community summaries). The point of the global path is that it answers questions ordinary vector RAG structurally cannot — “what are the main themes across this corpus?” has no single chunk that contains the answer, so top-K similarity retrieval will always fail on it. The cost is a substantial LLM-driven indexing pass, so it suits corpora that are queried far more often than they change.

How the KG embedding methods work, briefly. TransE models a relation as a translation in vector space, training so that for true triples and not for corrupted ones. It is elegant but cannot represent one-to-many relations properly — if (US, city, NYC) and (US, city, LA) both hold, then NYC and LA are forced to the same point. TransR and successors add relation-specific projection matrices to fix that. Mention the limitation, not just the name.

Follow-up: How do you handle a conflict between the graph and the model’s parametric knowledge? Instruct the model explicitly that the provided facts take precedence over what it believes, and verify afterwards that the answer’s entities actually appear in the supplied subgraph. Models do not reliably defer to context on their own, especially when the context contradicts something strongly represented in the weights — this is a measured effect, not a theoretical worry.

Why the interviewer asks this. It is really a question about where knowledge should live — weights versus retrieval — and whether you understand the update, provenance and cost consequences of that choice.

Saying it out loud. The real question is where you want the knowledge to live. You can bake triples into the weights by verbalizing them into sentences and training on them, which means no retrieval at inference — but then updating one fact means retraining, and there’s nothing stopping the model hallucinating around it. Or you keep the graph external and retrieve the relevant subgraph at query time, which is basically RAG with a graph instead of a text index. That’s what I’d default to: facts update instantly, you get provenance for free, and no training run. The engineering is entity linking plus a one- or two-hop neighbourhood expansion, with filtering, because two hops off a popular node pulls in thousands of edges. And GraphRAG is worth knowing — you build the graph from your corpus with an LLM and pre-summarize communities, which lets you answer corpus-wide questions that vector search structurally can’t.


Q83: How do you integrate past conversation history into LLMs?

In 30 seconds. “Even with a million-token window this doesn’t go away — you pay for every input token on every turn, models recall things buried mid-context unreliably, and a window lasts one session while users expect you to remember them next month. So I’d build tiers: last few turns verbatim, older turns embedded and retrieved by relevance, a rolling summary for the arc, and a structured user profile of durable facts injected into every session.”

The short version.

TierHoldsRetrieved howLifetime
Working memorylast 10–20 turnsalways in contextcurrent session
Episodic memoryolder topic segmentsvector search + recencymonths
Rolling summarycompressed narrativealways in contextcurrent session
Semantic / profiledurable extracted factsalways injectedindefinite

Pipeline: collect logs → strip PII → segment into turns and sessions → extract entities, intent, sentiment → integrate as context or user embedding.

Why it works.

Human memory is tiered for the same reason: you remember the last sentence verbatim, last week’s conversation as gist, and your friend’s dietary restrictions forever. Each tier trades fidelity for durability, and no single mechanism can do both. A chat system that keeps everything verbatim is paying full price for information it only needs the gist of.

The math, and what it buys you.

Answer:

Challenges:

  • Limited context window (2K-32K tokens)
  • Need long-term memory
  • User personalization

Integration Strategies:

1. Context Window Extension:

  • Store history in external memory
  • Retrieve relevant history
  • Concatenate to context

2. Memory-Augmented Models:

  • Main model: Processes current input
  • Memory bank: Stores conversation history
  • Attention: Attend to relevant history

3. Hierarchical Encoding:

  • Level 1: Individual messages
  • Level 2: Conversation turns
  • Level 3: Conversation sessions
  • Level 4: User profile

4. RAG for History:

  • Store conversations in vector DB
  • Retrieve relevant history
  • Add to context

Processing:

  1. Data collection (chat logs, transcripts)
  2. Data cleaning (remove PII, anonymize)
  3. History segmentation (turns, sessions)
  4. Feature extraction (sentiment, intent, entities)
  5. Integration (conversation modeling or user embedding)

Best Practice:

  • Keep last 10-20 turns in context
  • Use retrieval for older history
  • Learn user embeddings for personalization

A currency note and a sharper framing.

The stated “2K–32K tokens” context limit is out of date: by 2026 windows of 200K to 1M tokens are common. But that does not dissolve the problem, and explaining why is the strongest part of an answer here. Three reasons memory is still an architecture problem rather than a solved one: cost, since you pay for input tokens on every turn and resending a 200-turn history each time is quadratic in total spend across the conversation; latency and the lost-in-the-middle effect (Q59), so a fact stated 300 turns ago and buried mid-context is unreliably recalled; and persistence, since a context window lasts one session while a user expects the system to remember their preferences next month. That third one no context window ever solves.

A concrete tiered memory design, which is what to sketch.

Working memory — the last turns verbatim, where is set by budget rather than principle. Recency is cheap and disproportionately useful.

Episodic memory — older turns embedded and stored in a vector index, retrieved by relevance to the current message. The subtlety worth mentioning: embed at the level of a topic segment rather than a single message, because “yes, that one” is a useless retrieval unit on its own; and store each segment with a timestamp so recency can be blended into ranking.

Semantic memory / user profile — durable extracted facts (“prefers Python,” “works in oncology,” “is allergic to shellfish”), written as structured records rather than raw text. These are the things that should survive indefinitely and be injected into every session. The extraction step is an LLM call over recent conversation asking what is worth remembering, run asynchronously so it does not sit on the response path.

Rolling summary — a compressed running narrative of the conversation so far, updated periodically, so the model has the arc even when the verbatim turns have been evicted.

The hard parts, which is where the answer earns its keep. Contradiction handling: the user said “I use Java” in March and “I use Go” in September, and both are now in memory. You need a write path that updates or supersedes rather than only appends, with timestamps deciding precedence. Forgetting: memory that only grows becomes retrieval noise, so you need eviction by age and by access frequency. And privacy, which is not a footnote — a persistent memory store contains everything a user ever said, so you need PII detection at write time, explicit user visibility into what is stored, deletion that actually deletes (including from any derived index), and a defensible retention policy. Raising privacy unprompted is a strong signal in this question because the failure mode is a headline rather than a metric.

Follow-up: How do you evaluate a memory system? Construct multi-session test conversations where a fact is established early and required much later, and measure recall at varying distances. Then measure the failure modes separately: false memories (facts asserted that were never stated), stale memories (superseded facts recalled as current), and intrusion (memories retrieved into an unrelated conversation, which users find unsettling even when the fact is correct).

Saying it out loud. Even with a million-token window this doesn’t go away, because you pay for every input token on every turn, models are unreliable at recalling things buried in the middle of a long context, and a window lasts one session while users expect you to remember them next month. So I’d build tiers. Last few turns verbatim, because recency is cheap and useful. Older turns embedded into a vector store and retrieved by relevance — segmented by topic, not by message, because “yes, that one” is a useless retrieval unit. A rolling summary for the overall arc. And a structured user profile of durable facts, extracted asynchronously and injected into every session. The hard parts are contradiction — the user said Java in March and Go in September, so writes have to supersede, not just append — forgetting, so memory doesn’t become noise, and privacy, because that store ends up holding everything the user ever said.


Q84: What is a world model? How do you build one for LLMs?

In 30 seconds. “A world model is a learned simulator: give it a state and an action, it predicts the next state and the reward, so an agent can plan by rolling it forward in imagination instead of acting in the real world. The interesting question is whether LLMs already have one — probes find internal representations of things like board state that were never explicitly supervised, but they fail on counterfactual variants like arithmetic in a different base. The deep obstacle is that you can’t identify causation from passive observation; you need intervention.”

The short version.

  • State representation — entities, properties, relations, time; symbolic, embedding, or graph.
  • Transition model — predicts next state from state + action; deterministic, stochastic, or learned.
  • Observation model — maps hidden state to what you can actually see (partial observability).
  • Reward model — defines what counts as good; task-specific, shaped, or learned.
  • Planning — model-based RL, tree search, model-predictive control.

Why it works.

The point of a world model is that imagination is cheaper than experience. An agent with a good simulator can try ten thousand plans overnight; an agent without one has to try them in the world, where mistakes cost time, money, or a robot. That is the entire value proposition, and it also explains the failure mode: a simulator that is subtly wrong lets you plan confidently into a wall.

The math, and what it buys you.

Answer:

World Model:

  • Internal representation of how world works
  • Predicts future states
  • Understands cause and effect
  • Enables planning and reasoning

Key Components:

1. State Representation:

  • Entities and properties
  • Relationships
  • Temporal information
  • Methods: Symbolic, embedding, graph

2. Transition Model:

  • Predicts next state given current state and action
  • Types: Deterministic, stochastic, learned
  • Training: Neural network on state-action-next_state tuples

3. Observation Model:

  • Maps world state to observations
  • Handles partial observability
  • Models what we can observe

4. Reward Model:

  • Defines what’s good/bad
  • Guides learning
  • Types: Task-specific, shaped, learned

5. Planning:

  • Use world model to find good actions
  • Methods: Model-based RL, tree search, MPC

Integration with LLMs:

LLM → World Model Interface → World Model → Planning → Actions

Training:

  1. Learn world model from data
  2. Integrate with LLM
  3. Joint training end-to-end

Where the term comes from, and what the real debate is.

The phrase comes from model-based reinforcement learning, where a world model is a learned simulator: given the current state and a proposed action, it predicts the next state and the reward. Its value is that an agent can plan by rolling the simulator forward — trying thousands of imagined action sequences and picking the best — instead of taking thousands of expensive or dangerous real actions. Ha and Schmidhuber’s “World Models” and the Dreamer line of work are the canonical references, and MuZero is the clearest demonstration that learning the model and planning in it can beat planning in a known one.

The live question for LLMs: do they already have one? This is what the interviewer is actually curious about, and the honest answer is “partially, and it is contested.”

Evidence for. Probing studies find internal representations of things the model was never explicitly taught — the state of a board in a game from move sequences alone, spatial and temporal coordinates for entities, and other latent structure recoverable by linear probes. That is more than surface statistics, and it is reasonably strong evidence of some internal model of the domain being described.

Evidence against. LLM predictions violate physical and causal constraints in ways a real simulator would not; performance degrades sharply on counterfactual variants of familiar tasks (arithmetic in base 9, chess from a slightly non-standard opening), which suggests reliance on memorized patterns rather than a general model that transfers; and the models are notoriously weak at multi-step planning that requires backtracking, which is exactly what a usable world model should make easy.

The defensible position is that LLMs learn something like a world model of text-describable regularities, learned from a passive corpus, which is not the same as a grounded causal model of physical dynamics — and that the difference shows up precisely where you would predict, on counterfactuals and long-horizon planning.

The deepest obstacle, which is worth naming. A world model learned only from observation cannot in general distinguish correlation from causation, because intervention is what identifies causal structure and passive text contains descriptions of interventions rather than interventions themselves. This is the standard Pearl argument and it is the strongest theoretical reason to expect that scaling passive prediction alone will not produce a fully causal world model. It is also why embodiment, tool use, and agents that actually act and observe consequences are the direction of travel — an agent that calls an API and sees the result is performing an intervention, however small.

A practical version, because “build a world model for an LLM” needs to become concrete. A deployable approximation: give the model an explicit external state representation (a structured document, a database, a game state), require it to propose actions against that state, execute them in a real or simulated environment, feed the observed result back, and let the model revise. The environment supplies the transition dynamics the model lacks, and the LLM supplies the priors and the language interface. That is the architecture behind most agentic systems in 2026, and describing it grounds an otherwise abstract question. Agent architectures are moving fast; treat any specific framework as a snapshot.

Follow-up: Why not just train on video? Video gives you passive physical dynamics, which is real progress on the observation side and is why video generation models are increasingly discussed as world models. But it is still observational: you see what happened, not what would have happened had the agent acted differently. Interaction is the missing ingredient, and it is expensive to collect.

Why the interviewer asks this. This is a research-taste question — they want to see whether you can hold a genuinely open problem carefully, with evidence on both sides, rather than picking a slogan.

Saying it out loud. A world model is a learned simulator: give it a state and an action, it predicts the next state and the reward, so an agent can plan by rolling it forward in imagination instead of acting in the real world. The interesting question is whether LLMs already have one. There’s decent evidence they partly do — probes find internal representations of things like board state or spatial layout that were never explicitly supervised. But they also fail on counterfactual variants of familiar tasks, like arithmetic in a different base, which suggests a lot of pattern matching rather than a general model. The deep obstacle is that you can’t identify causation from passive observation alone — you need intervention — which is the main argument that scaling text prediction won’t get you there and why agents that act and see consequences matter.


Q85: What are the future directions of LLMs? What’s the path to AGI?

In 30 seconds. “I’d be honest that nobody knows, and talk about what’s actually blocking things. Four bottlenecks: data, since high-quality human text is finite and mostly used; inference cost, because capability is increasingly bought at inference time and that scales with every user; continual learning, which is genuinely unsolved — fine-tuning causes catastrophic forgetting, so the industry routes around it with retrieval; and reliability compounding, where a 95%-per-step agent finishes a twenty-step task about a third of the time. That last one is exactly why agents demo well and deploy badly.”

The short version.

AspirationConcrete bottleneck today
General intelligenceevaluation — benchmarks saturate and leak
World understanding, causalityno intervention in passive training data
Continual learningcatastrophic forgetting, unsolved
Embodied intelligenceinteraction data is expensive to collect
Long-horizon planningper-step reliability compounds badly
Efficient scalinginference cost now dominates training cost

Research areas named in the standard answer: scaling and sparse models, multimodality, reasoning, planning, memory — plus a six-module AGI architecture (perception, world model, memory, reasoning, action, learning).

Why it works — and why to hold it loosely.

That six-module diagram closely resembles classical cognitive architectures like SOAR and ACT-R from decades ago. The lesson from that history is that drawing the boxes was never the hard part: each box turned out to be an open research problem and the interfaces between them were harder still. Meanwhile the recent trend has run the other way — end-to-end learned systems repeatedly beating hand-designed modular ones. That is the Bitter Lesson, and it is the main reason to be sceptical of any neat block diagram.

The math, and what it buys you.

Answer:

Future Directions:

1. General Intelligence:

  • Human-level intelligence
  • General problem solving
  • Transfer learning
  • Few-shot adaptation

2. World Understanding:

  • Understand how world works
  • Predict consequences
  • Plan actions
  • Reason about causality

3. Continual Learning:

  • Learn from new data continuously
  • Don’t forget old knowledge
  • Adapt to new domains

4. Embodied Intelligence:

  • Interact with physical world
  • Learn from experience
  • Understand physics

Key Research Areas:

Scaling:

  • Efficient scaling
  • Better architectures
  • Sparse models

Multimodality:

  • All modalities
  • Unified representation

Reasoning:

  • Strong logical reasoning
  • Causal reasoning
  • Mathematical reasoning

Planning:

  • Long-term planning
  • Hierarchical planning

Memory:

  • Long-term memory
  • Episodic memory
  • Semantic memory

AGI Architecture Vision:

1. Perception Module (multimodal input)
2. World Model (state, transition, planning)
3. Memory System (episodic, semantic, working)
4. Reasoning Engine (logical, causal, analogical)
5. Action Module (text, tools, physical)
6. Learning System (continual, meta-learning)

Path to AGI:

  • Multimodal integration
  • World models
  • Strong reasoning
  • Long-term planning
  • Long-term memory
  • Continual learning

Reading this question correctly.

Nobody knows the path to AGI, and an interviewer asking this is not checking whether you do. They are checking three things: whether you can discuss speculative material without either dismissing it or overclaiming; whether you know what the current concrete bottlenecks are, as opposed to the aspirational bullet list; and whether you can attach evidence to a prediction. The list above is a fine map of aspirations. What follows is the part that makes an answer sound like it came from someone working in the field.

The bottlenecks that are actually binding right now.

Data. High-quality human-written text is finite and much of it is already used. This has pushed the field toward synthetic data, multiple epochs over curated corpora, and non-text modalities. The open risk is model collapse — training on model output degrading quality across generations — which appears controllable with careful mixing of real data but is a genuine constraint rather than a hypothetical.

Inference cost. Capability is increasingly purchased at inference time rather than training time: reasoning models spend more compute per query, agents make many model calls per task. That flips the economics, since inference cost scales with usage while training cost is amortized. Mixture-of-experts, distillation, quantization, speculative decoding and caching are all responses to this single pressure, and it is probably the most consequential shift since Chinchilla.

Continual learning. Models are frozen at a cutoff. Fine-tuning on new data causes catastrophic forgetting — new gradients overwrite the weights encoding old capabilities — and there is still no method that reliably adds knowledge without degrading something else. This is why the industry routes around the problem with retrieval and long context rather than solving it, and it is a real, unglamorous open problem.

Reliability and long-horizon execution. An agent with 95% per-step reliability completes a 20-step task about 36% of the time, since . Long-horizon autonomy therefore needs either very high per-step reliability or robust error detection and recovery, and this compounding is the concrete reason agents demo well and deploy badly. It is a much more useful thing to say than “reasoning needs to improve.”

Evaluation. We increasingly cannot measure what we are building. Benchmarks saturate, leak into training data, and fail to predict real task performance. Without trustworthy measurement, “progress toward AGI” is not a claim anyone can check — which is itself part of why the question has no rigorous answer.

On the AGI architecture diagram above. It is a reasonable functional decomposition and it is worth noting that it closely resembles classical cognitive architectures (SOAR, ACT-R) from decades ago. The lesson from that history is that the hard part was never drawing the boxes; it was that each box turned out to be an open research problem, and that the interfaces between them were harder still. Also worth saying: the recent trend has run the other way — end-to-end learned systems have repeatedly beaten hand-designed modular ones, which is the Bitter Lesson, so a diagram of six hand-specified modules should be held loosely.

Everything in this answer is time-sensitive and reflects the state of the field as of mid-2026. Treat it as a snapshot; the bottlenecks change faster than the aspirations do.

Follow-up: What would change your mind that we are close? Something concrete and falsifiable: a model that reliably completes multi-day, multi-tool tasks with a real error-recovery loop; genuine continual learning without forgetting; or robust performance on counterfactual variants of tasks it has mastered, which would suggest general mechanisms rather than memorized patterns. Naming a falsifiable criterion is what separates a considered view from an opinion.

Why the interviewer asks this. It is a calibration test, not a knowledge test — they are watching how you handle a question where the honest answer is uncertainty.

Saying it out loud. I’d be honest that nobody knows, and talk about what’s actually blocking things instead. Four bottlenecks I’d name. Data — high-quality human text is finite and mostly used, which is what’s driving synthetic data. Inference cost, because capability is increasingly bought at inference time now, and unlike training that scales with every user. Continual learning, which is genuinely unsolved — fine-tuning on new data causes catastrophic forgetting, so the industry routes around it with retrieval rather than fixing it. And reliability compounding: an agent that’s ninety-five percent reliable per step finishes a twenty-step task about a third of the time, which is exactly why agents demo well and deploy badly. On the architecture diagrams with perception, memory, reasoning, and planning modules — those look a lot like cognitive architectures from the eighties, and the lesson there was that drawing the boxes was never the hard part.


See 38_multimodal_and_embeddings/multimodal_integration_and_world_models.md for complete details!


GPT Implementation, Training, and Decoding

Q86: Implement a complete GPT model from scratch. What are all the components?

In 30 seconds. “Bottom up: token IDs through an embedding table, plus positional information — because attention is permutation-equivariant, so without it ‘dog bites man’ and ‘man bites dog’ are the same input. Then the core block: project to Q, K, V, split across heads, score every token against every other, divide by the square root of the head dimension, mask out the future, softmax, multiply by V. Then a position-wise feed-forward. The mental model is that attention moves information between positions and the MLP processes each position alone.”

The short version.

#ComponentShape / detail
1Token embedding(vocab, ) lookup →
2Positional encodinglearned (max_len, ), or rotary in modern models
3Multi-head attention heads, , causal mask
4Feed-forward, GELU/SwiGLU, position-wise
5Transformer blockattention + FFN, each in a pre-norm residual
6Stack of blocks12–96 layers
7Final layer normstabilizes the output of the residual stream
8Output projection vocab logits, usually weight-tied to the embedding

See 04_transformers/gpt_complete.py for complete implementation!

Why it works.

Two mechanisms alternating. Attention is the communication step: each token asks a question (its query), every token advertises what it has (its key), and the answer is a weighted blend of what they hold (their values). The feed-forward is the computation step: each position thinks privately about what it just heard. Residual connections make the stack a shared bus that every layer reads from and writes to, rather than a chain where information must survive intact through 96 transformations — which is the reason very deep Transformers train at all.

The math, and what it buys you.

Answer:

Complete GPT consists of:

1. Token Embedding:

  • Converts token indices to dense vectors
  • Learned embeddings, shape (vocab_size, d_model)

2. Positional Encoding:

  • Adds position information to embeddings
  • Sinusoidal or learned positional embeddings
  • Shape: (max_seq_len, d_model)

3. Multi-Head Attention:

  • Self-attention mechanism
  • Multiple heads (typically 12-96)
  • Each head: Q, K, V projections → attention → concatenate
  • Complexity: O(n²d)

4. Feed-Forward Network:

  • Two linear layers with ReLU
  • Expands then contracts: d_model → d_ff → d_model
  • Applied position-wise

5. Transformer Block:

  • Multi-head attention + Feed-forward
  • Residual connections + Layer normalization
  • Dropout for regularization

6. Stack of Transformer Blocks:

  • Multiple blocks (typically 12-96 layers)
  • Each block refines representations

7. Final Layer Norm:

  • Normalizes final representations

8. Output Projection:

  • Maps to vocabulary size
  • Produces logits for next token prediction

See 04_transformers/gpt_complete.py for complete implementation!

Narrative walkthrough: building it in the order you would write it.

The component list above is a parts inventory. What an interviewer wants is the assembly order and the reason for each piece, told so that someone could reconstruct the code from the description. Here is that narrative, with shapes tracked throughout. Take a batch of sequences of length , model width , and attention heads.

1. Turn token IDs into vectors. The input is an integer tensor of shape . An embedding table of shape (vocab, ) is looked up row-wise to give . This is a lookup, not a matrix multiply — the one-hot formulation you see in papers is mathematically equivalent and computationally wasteful.

2. Add position. Self-attention is permutation equivariant: shuffle the input tokens and the outputs shuffle identically, because attention is a weighted sum over a set with no notion of order. Without positional information “dog bites man” and “man bites dog” are literally the same input. GPT-2 used a learned position embedding table of shape (max_len, ), added to the token embeddings. Modern models mostly use rotary embeddings instead, which rotate the query and key vectors by an angle proportional to position so that attention scores depend on relative distance — this extrapolates to longer sequences far better than a learned absolute table, which has no entry at all for a position it never saw in training.

3. Causal self-attention, the core. Project the input three times to get queries, keys and values. In practice one linear layer of width then split, because a single large matmul is faster than three small ones. Reshape each to so every head works in its own subspace. Compute to get — the score of every token against every other. Divide by : the dot product of two vectors with unit-variance entries has variance equal to the dimension, so without this scaling the logits grow with head size, the softmax saturates, and gradients vanish. Then apply the causal mask by setting all positions where key index exceeds query index to , so softmax assigns them exactly zero — this is what makes position unable to see position , which is what makes it valid to compute the loss at every position simultaneously. Softmax over the last dimension, multiply by to get , transpose and reshape back to , and apply a final output projection that lets the heads’ results mix.

4. The feed-forward block. Two linear layers with a nonlinearity between, expanding . It is applied independently at every position — attention moves information between positions, the feed-forward network processes each position on its own, and that division of labour is the cleanest way to describe a Transformer block. Note that the answer above says ReLU; GPT-2 and successors actually use GELU, and modern models typically use SwiGLU, a gated variant. The expansion is convention rather than derivation, and this block holds roughly two-thirds of the model’s parameters.

5. Residual connections and normalization. Each sub-block computes x = x + sublayer(norm(x)). Two separate ideas here. The residual gives gradients a path straight from the loss to every layer via the identity term, which is what makes 96-layer training feasible at all — it is also useful to think of the residual stream as a shared bus that each layer reads from and writes to. The normalization stabilizes activation scale. GPT-2 moved the norm before the sublayer (pre-norm) rather than after, which is the reason deep Transformers train without a delicate learning-rate warmup schedule; post-norm as in the original paper is noticeably harder to train deep.

6. Stack, normalize, project out. Repeat the block times, apply a final layer norm, then a linear map from to vocabulary size producing logits . That output matrix is very commonly tied to the input embedding table — the same weights, transposed — which saves a large number of parameters (vocab can be a substantial fraction of a small model) and typically improves quality.

Verified implementation. The following runs and trains. On a synthetic copy task — learning to continue a repeating sequence — this exact code drives cross-entropy from (the uniform-guessing baseline for a 17-token vocabulary) to in 300 AdamW steps, and then generates the correct continuation. That end-to-end sanity check, overfitting one tiny batch before touching real data, is the first thing to do with any model implementation.

import math, torch, torch.nn as nn, torch.nn.functional as F

class CausalSelfAttention(nn.Module):
    def __init__(self, d_model, n_head, max_len):
        super().__init__()
        assert d_model % n_head == 0
        self.n_head, self.d_head = n_head, d_model // n_head
        self.qkv  = nn.Linear(d_model, 3 * d_model)   # one matmul, then split
        self.proj = nn.Linear(d_model, d_model)       # lets heads mix
        mask = torch.tril(torch.ones(max_len, max_len)).view(1, 1, max_len, max_len)
        self.register_buffer("mask", mask)            # not a parameter

    def forward(self, x):
        B, T, C = x.shape
        q, k, v = self.qkv(x).split(C, dim=2)
        # (B, T, C) -> (B, n_head, T, d_head): every head in its own subspace
        q = q.view(B, T, self.n_head, self.d_head).transpose(1, 2)
        k = k.view(B, T, self.n_head, self.d_head).transpose(1, 2)
        v = v.view(B, T, self.n_head, self.d_head).transpose(1, 2)
        att = (q @ k.transpose(-2, -1)) / math.sqrt(self.d_head)   # (B,h,T,T)
        att = att.masked_fill(self.mask[:, :, :T, :T] == 0, float("-inf"))
        att = att.softmax(dim=-1)                                  # masked -> exactly 0
        y = (att @ v).transpose(1, 2).contiguous().view(B, T, C)
        return self.proj(y)

class Block(nn.Module):
    def __init__(self, d_model, n_head, max_len):
        super().__init__()
        self.ln1, self.ln2 = nn.LayerNorm(d_model), nn.LayerNorm(d_model)
        self.attn = CausalSelfAttention(d_model, n_head, max_len)
        self.mlp  = nn.Sequential(nn.Linear(d_model, 4 * d_model), nn.GELU(),
                                  nn.Linear(4 * d_model, d_model))
    def forward(self, x):
        x = x + self.attn(self.ln1(x))   # pre-norm: norm inside the residual branch
        x = x + self.mlp(self.ln2(x))
        return x

class GPT(nn.Module):
    def __init__(self, vocab, d_model=64, n_head=4, n_layer=2, max_len=32):
        super().__init__()
        self.max_len = max_len
        self.tok = nn.Embedding(vocab, d_model)
        self.pos = nn.Embedding(max_len, d_model)
        self.blocks = nn.ModuleList([Block(d_model, n_head, max_len) for _ in range(n_layer)])
        self.ln_f = nn.LayerNorm(d_model)
        self.head = nn.Linear(d_model, vocab, bias=False)
        self.head.weight = self.tok.weight            # weight tying

    def forward(self, idx, targets=None):
        B, T = idx.shape
        x = self.tok(idx) + self.pos(torch.arange(T, device=idx.device))
        for blk in self.blocks:
            x = blk(x)
        logits = self.head(self.ln_f(x))              # (B, T, vocab)
        loss = None
        if targets is not None:
            loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1))
        return logits, loss

Parameter count, so the shapes stop being abstract. Per block: attention has (three projections plus the output projection) and the feed-forward has (two layers of ), so about per block, times blocks, plus for embeddings. For GPT-2 small (, , ) that is plus of embeddings — the familiar 124M. Being able to do this arithmetic live is a strong signal.

Follow-up: What would you change to make this production-grade? Replace the hand-written attention with F.scaled_dot_product_attention so it dispatches to FlashAttention; swap learned positions for rotary; use RMSNorm instead of LayerNorm; add a KV cache for generation (Q88); add dropout; and use grouped-query attention to shrink that cache. None of these change what the model computes conceptually, and all of them matter for cost.

Why the interviewer asks this. Anyone can call a library; writing attention by hand is the standard check that you know what the shapes are and why the mask and the scaling factor exist.

Saying it out loud. I’d build it bottom up. Token IDs go through an embedding table, and you add positional information — because attention is permutation-equivariant, so without it “dog bites man” and “man bites dog” are the same input. Then the core block: project to queries, keys, and values, split across heads, take Q times K transposed to score every token against every other, divide by the square root of the head dimension so the softmax doesn’t saturate, mask out the future with negative infinity, softmax, and multiply by V. Then a position-wise feed-forward that expands to four times the width and back. The mental model is that attention moves information between positions and the MLP processes each position alone. Wrap both in residual connections with pre-normalization, stack it however many times, final layer norm, and project to vocabulary size — usually tying that matrix to the input embeddings.


Q87: How is GPT trained? Explain the training process in detail.

In 30 seconds. “The objective is just next-token prediction, but the mechanical bit worth stating is that you don’t run the model once per token. You take a chunk, feed all but the last token as input and all but the first as targets, and one forward pass gives a prediction at every position — the causal mask is what makes that legitimate. Loss is cross-entropy averaged over positions; exponentiate it and you get perplexity. Practically: AdamW with , gradient clipping at norm 1, warmup then cosine decay.”

The short version.

StageWhat matters
Datalarge corpora, BPE/SentencePiece, packed into a token stream (not padded)
Forwardembeddings + positions → blocks → logits at every position
Losscross-entropy on shifted targets; is the step-one sanity check
Backwardbackprop, clip global norm at 1.0, AdamW
Schedulelr 3e-4 to 1e-4, linear warmup then cosine decay to ~10% of peak
Scaletrillions of tokens, batch measured in tokens, not sequences

Why it works.

Next-token prediction is self-supervised, which means the labels are free — every token in every document is a training example, and there is no annotation bottleneck. And the causal mask lets you harvest all of those examples from one forward pass, so the objective is not only label-free but compute-efficient. That combination is the reason this particular objective, and not a cleverer one, is what scaled.

The math, and what it buys you.

Answer:

Training Objective:

  • Next token prediction (language modeling)
  • Given tokens [t₁, t₂, …, tₙ], predict [t₂, t₃, …, tₙ₊₁]
  • Autoregressive: each token depends on all previous tokens

Training Process:

1. Data Preparation:

  • Large text corpora (books, web, articles)
  • Tokenization (BPE, SentencePiece)
  • Batching and padding to fixed length

2. Forward Pass:

  • Token embeddings + positional encoding
  • Pass through transformer blocks
  • Output logits for each position

3. Loss Function:

  • Cross-entropy loss
  • L = -(1/n) Σ log P(tᵢ | t₁, …, tᵢ₋₁)
  • Compares predicted distribution to true next token

4. Backward Pass:

  • Compute gradients via backpropagation
  • Gradient clipping (max_norm=1.0)
  • Update parameters with optimizer (Adam/AdamW)

5. Training Details:

  • Learning rate: 3e-4 to 1e-4
  • Learning rate scheduling (warmup + decay)
  • Dropout for regularization
  • Weight initialization (normal, std=0.02)

Key Insight:

  • Massive scale: GPT-3 trained on trillions of tokens
  • Self-supervised: no labels needed, just text
  • Learns language patterns, syntax, semantics, reasoning

See 04_transformers/gpt_training_decoding.md for complete details!

Walkthrough: what one training step actually does.

The five phases above are correct; here is the mechanical detail that turns them into something you could implement.

The shift, precisely. You do not run the model times to predict tokens. You take a chunk of tokens, feed positions as input and use positions as targets — literally x = chunk[:-1]; y = chunk[1:]. One forward pass produces logits at every position, and because the causal mask guarantees position never saw position , all predictions are simultaneously valid. This is the efficiency that makes language model pretraining possible at all, and it is why decoder training extracts far more signal per token than BERT’s masked objective, which supervises only the masked 15%.

The loss, with units. Cross-entropy averaged over all positions and all sequences in the batch. Its natural unit is nats per token; divide by for bits per token, and exponentiate for perplexity, the effective number of equally-likely choices the model is deciding among. A useful anchor: a uniform model over a 50,257-token vocabulary has perplexity 50,257 and loss ; a good modern model on general web text is in the low single digits of perplexity. If your loss does not start near on step one, your initialization or your data pipeline is broken — that is a five-second diagnostic worth knowing.

Packing, and the detail everyone gets wrong. The answer above says “batching and padding to fixed length.” For pretraining you generally do not pad; you concatenate the whole corpus into one long token stream and slice fixed-length windows out of it, so no compute is wasted on padding tokens. The subtlety is that documents then bleed into each other — the model is asked to predict the first token of document from the tail of document , which is noise. Fixes are to insert an end-of-text token at boundaries (cheap, mostly sufficient) or to use a block-diagonal attention mask that prevents cross-document attention (correct, slightly more work). For fine-tuning on instruction data you do pad, and there you must mask the padding out of the loss and usually mask the prompt tokens too, so the model is scored only on the response.

The optimizer settings, with reasons. AdamW rather than Adam, because decoupling weight decay from the adaptive scaling is what makes decay behave as intended. rather than the 0.999 default, since language-model gradients are noisy and the shorter second-moment window adapts faster. No weight decay on biases, layer-norm parameters, or embeddings — decaying a normalization gain toward zero is meaningless. Gradient clipping at global norm 1.0, which catches the loss spikes that come from a pathological batch and is cheap insurance. And a learning-rate schedule of linear warmup over a few hundred to a few thousand steps followed by cosine decay to about 10% of peak: the warmup exists because Adam’s second-moment estimate is unreliable in the first steps, so a full-size step early can knock the model into a bad region it never recovers from.

Batch size in tokens, not sequences. Large-model training uses batches of hundreds of thousands to millions of tokens, assembled with gradient accumulation across many steps and many devices. Reporting batch size in sequences is meaningless unless the sequence length is also given, and interviewers notice the difference.

Memory, which is where real training runs die. Per parameter in mixed precision you store roughly: 2 bytes for the fp16/bf16 weight, 4 bytes for the fp32 master copy, and 8 bytes for Adam’s two moment estimates — around 14–16 bytes per parameter before activations. A 7B model therefore needs on the order of 100 GB just for optimizer state, which is why sharding (ZeRO/FSDP) exists, distributing optimizer state, gradients and parameters across devices. Activation memory scales with batch length width layers and is traded against compute by gradient checkpointing, which discards intermediate activations and recomputes them in the backward pass for roughly 30% extra compute. Being able to say why a model that “fits in memory” still fails to train is a strong practical signal.

Verified. The model in Q86 trained with exactly this recipe — AdamW, gradient clipping at 1.0, shifted targets — drops from the uniform baseline of to a loss of on a memorization task in 300 steps, confirming the loop is wired correctly.

Follow-up: Your loss suddenly spikes at step 40,000 and never recovers. What happened? Most often a bad data batch (a long run of repeated or corrupted tokens) combined with an optimizer state that then goes bad. The standard practice is to checkpoint frequently, and on a spike, roll back to the last good checkpoint and skip the offending data shard. Lowering the learning rate or clipping more aggressively reduces the frequency. Loss spikes are common enough at scale that skip-and-resume is an ordinary operational procedure, not an emergency.

Why the interviewer asks this. The interesting content is not the objective — everyone knows it is next-token prediction — but whether you know what makes a large run actually complete.

Saying it out loud. The objective is just next-token prediction, but the mechanical bit worth stating is that you don’t run the model once per token. You take a chunk, feed all but the last token as input and all but the first as targets, and one forward pass gives you a prediction at every position — the causal mask is what makes that legitimate, because position t never saw position t plus one. Loss is cross-entropy averaged over positions; exponentiate it and you get perplexity. On the practical side: AdamW with beta-two around 0.95, gradient clipping at norm one, and linear warmup then cosine decay, because Adam’s second-moment estimate is unreliable in the first few hundred steps and a full-size step early can wreck the run. And I’d concatenate documents into one stream rather than padding, with an end-of-text token at the boundaries so the model isn’t asked to predict one document from another.


Q88: How does GPT decode/generate text? Explain the decoding process.

In 30 seconds. “Generation is autoregressive — one token at a time, each conditioned on everything before. The implementation detail that matters most is the KV cache: naively you’d re-run the model over the whole sequence for every new token, which is quadratic, but the causal mask means earlier tokens’ keys and values never change, so you cache them. That splits serving into prefill, which is compute-bound, and decode, which is memory-bandwidth-bound. For sampling I’d default to nucleus over top-k, because a fixed k ignores the shape of the distribution.”

The short version.

SamplerRuleBest for
Greedyalways take the argmaxtranslation, extraction, anything you’ll parse
Temperaturedivide logits by before softmaxthe global diversity dial
Top-ksample from the highestsimple truncation, ignores distribution shape
Top-p (nucleus)smallest set with cumulative mass open-ended text — adapts to confidence

Loop: tokenize the prompt → forward pass → logits for the next token → temperature → truncate → sample → append → repeat until EOS, a stop sequence, or max length. Causal masking (upper-triangular ) is what keeps each step honest.

Why it works.

The model never outputs text; it outputs a probability distribution over the vocabulary, and decoding is entirely about how you collapse that distribution into one choice. Taking the argmax every time yields fluent, dead prose because the most probable continuation is by construction the least surprising one. Sampling restores surprise — and truncation exists because the aggregated tail of a 50,000-token vocabulary holds real probability mass made of individually terrible tokens, and once you draw one the model must continue from its own mistake.

The math, and what it buys you.

Answer:

Autoregressive Generation:

  • Generate one token at a time
  • Each token depends on all previous tokens
  • Start with prompt, generate until stop condition

Decoding Process:

1. Initial Prompt:

  • User provides starting text
  • Tokenized into sequence [p₁, p₂, …, pₖ]

2. Forward Pass:

  • Process prompt through model
  • Get logits for next token
  • Shape: (vocab_size,) - scores for each token

3. Convert to Probabilities:

  • Apply softmax: P(t) = exp(logit_t) / Σ exp(logit_i)
  • Temperature scaling: P(t) = softmax(logits / T)
    • T=1.0: original distribution
    • T>1.0: more random (higher diversity)
    • T<1.0: more deterministic (lower diversity)

4. Sample Token:

  • Greedy: Always pick highest probability
  • Sampling: Random sample from distribution
  • Top-k: Sample from top-k tokens
  • Top-p (Nucleus): Sample from tokens with cumulative prob > p

5. Append and Repeat:

  • Append sampled token to sequence
  • Process new sequence again
  • Repeat until stop condition

6. Stop Conditions:

  • Maximum length reached
  • End-of-sequence token generated
  • Specific stop sequence

Causal Masking:

  • During decoding, mask future positions
  • Upper triangular matrix: -inf for future, 0 for past
  • Ensures model only sees previous tokens

See 04_transformers/gpt_training_decoding.md for complete details!

The missing piece: the KV cache, and why generation is memory-bound.

The description above is correct but omits the single most important implementation fact about decoding, which is that a naive implementation is quadratically wasteful and nobody does it.

Re-read step 5: “append sampled token, process new sequence again.” Done literally, generating token requires a forward pass over all tokens, so producing tokens costs passes worth of work. But the causal mask means the keys and values for tokens do not change when you append token — nothing later can alter an earlier token’s representation. So you cache them. With a KV cache, each new step computes , and for the single new token only, appends its and to the cache, and attends over the cached history. The per-step cost drops from to in sequence passes, which is the difference between a usable system and a toy.

This reshapes the whole performance picture, and it is why serving engineers talk the way they do:

Two distinct phases. Prefill processes the entire prompt in one parallel pass — compute-bound, and it sets time-to-first-token. Decode produces one token per pass — memory-bandwidth-bound, because each step reads the entire weight matrix from memory to do a tiny amount of arithmetic on a single token. This is why decode throughput tracks memory bandwidth rather than FLOPs, why batching many requests together helps so much (the weights are read once and amortized across the batch), and why speculative decoding works — a small draft model proposes several tokens and the large model verifies them in one parallel pass, converting several memory-bound steps into one.

The cache is large. Its size is per sequence — two for K and V, times layers, times sequence length, times width. For a 7B-class model at 32K context this reaches several gigabytes per concurrent request, which is usually what limits how many users you can serve on one GPU, not the model weights. This is the direct motivation for multi-query and grouped-query attention, which share key and value heads across query heads and shrink the cache by a large factor, and for KV cache quantization and paged attention.

Temperature and top-p, with numbers. For logits :

probabilities
0.50.865, 0.117, 0.016, 0.002
1.00.644, 0.237, 0.087, 0.032
2.00.455, 0.276, 0.167, 0.102

Low temperature sharpens toward the argmax (as it becomes greedy); high temperature flattens toward uniform. At the sorted cumulative probabilities are 0.644, 0.881, 0.968, 1.0, so nucleus sampling with keeps 3 tokens — the smallest set whose mass reaches 0.9. (Computed directly.)

Why top-p usually beats top-k. A fixed ignores the shape of the distribution. After “the capital of France is” the distribution is nearly a spike, and admits 49 tokens that are all wrong; after “she opened the door and saw a” the distribution is genuinely flat and truncates plausible continuations. Top-p adapts: it keeps few tokens when the model is confident and many when it is not. The deeper justification for truncation at all is that the aggregated tail of a 50,000-token vocabulary holds meaningful probability mass made of individually terrible tokens, and sampling from an untruncated distribution eventually draws one — after which the model must continue from its own mistake, which is how a generation derails.

Repetition, and the honest caveat. Greedy and beam decoding on open-ended text produce degenerate repetition loops, which is the core motivation for sampling. The common patches are a repetition penalty (divide the logits of already-generated tokens), a frequency or presence penalty, and n-gram blocking (forbid any n-gram that has already appeared). All are blunt: n-gram blocking will happily prevent a legitimately repeated name or a code identifier that must recur. Worth naming the tradeoff rather than presenting the penalties as free.

Verified sampling implementation. The following runs against the Q86 model and produces the correct continuation with both top-k and top-p:

@torch.no_grad()
def generate(model, idx, n_new, temperature=1.0, top_k=None, top_p=None):
    for _ in range(n_new):
        logits, _ = model(idx[:, -model.max_len:])   # crop to context window
        logits = logits[:, -1, :] / temperature      # only the last position matters
        if top_k is not None:
            kth = torch.topk(logits, top_k).values[:, -1:]
            logits = logits.masked_fill(logits < kth, float("-inf"))
        if top_p is not None:
            s, si = torch.sort(logits, descending=True)
            probs = s.softmax(-1)
            drop = probs.cumsum(-1) - probs > top_p  # keep the token that crosses p
            s = s.masked_fill(drop, float("-inf"))
            logits = torch.full_like(logits, float("-inf")).scatter(1, si, s)
        idx = torch.cat([idx, torch.multinomial(logits.softmax(-1), 1)], dim=1)
    return idx

Note the cumsum - probs > top_p rather than cumsum > top_p: it keeps the token that crosses the threshold, so the retained mass is at least rather than just under it. Off-by-one here silently changes your sampling distribution.

Follow-up: When would you use greedy or beam search instead of sampling? When there is a roughly correct answer and you want the most probable one: translation, structured extraction, code completion against a spec, or anything where you will parse the output. Use sampling for open-ended generation, where the highest-probability output is reliably bland and repetitive. Also note that greedy is not deterministic in practice across batch sizes or hardware, because floating-point reduction order changes — a fact that surprises people debugging reproducibility.

Why the interviewer asks this. Decoding is where most people’s understanding stops at “it predicts the next token,” so it is a good place to find out who has actually served a model.

Saying it out loud. Generation is autoregressive — one token at a time, each conditioned on everything before it. The implementation detail that matters most is the KV cache: naively you’d re-run the model over the whole sequence for every new token, which is quadratic, but the causal mask means earlier tokens’ keys and values never change, so you cache them and each step only processes the one new token. That splits serving into two phases with completely different characteristics — prefill, which processes the prompt in parallel and is compute-bound, and decode, which is memory-bandwidth-bound because you read all the weights to produce one token. That’s why batching helps so much and why speculative decoding works. For sampling, temperature sharpens or flattens the distribution, and I’d default to nucleus sampling over top-k, because a fixed k ignores the shape — after “the capital of France is” you want one token, and after “she opened the door and saw a” you want many.


Q89: What is the complexity of attention? Explain O(n²d) and different attention types.

In 30 seconds. “Attention itself is — an score matrix, each entry a -dimensional dot product. But the projections are , and which dominates depends on whether is bigger than ; the crossover is right at . That’s why the quadratic term only became urgent once contexts got long. The other thing I’d say is that attention is memory-bandwidth-bound, not compute-bound — which is exactly what FlashAttention fixes.”

The short version.

VariantTimeSpaceNotes
Standard attentionplus for projections
Multi-headsame total, parallel across heads
Linear attentionreassociates ; changes the model
Sparse attention or sub-quadraticlocal window + global tokens
FlashAttentionexact, tiled, much faster in wall-clock

Why it works.

The comes from the fact that attention compares every token to every other token — it is an all-pairs operation, and all-pairs is quadratic by definition. Every cheaper variant buys its speedup by refusing to compute some of those pairs (sparse), or by refusing to form the matrix at all (linear, via reassociation). FlashAttention is the odd one out and the reason it won: it computes exactly the same pairs, and simply never writes the matrix down.

The math, and what it buys you.

Answer:

Standard Self-Attention: O(n²d)

Why O(n²d)?

  • Compute QK^T: (n, d) @ (d, n) → (n, n) matrix
  • Each element: dot product of d-dimensional vectors
  • n² elements × d operations = O(n²d)
  • Apply to V: (n, n) @ (n, d) → O(n²d)
  • Total: O(n²d) time, O(n²) space

The n² term:

  • Attention matrix: n×n (all pairs of tokens)
  • Each token attends to all other tokens
  • Quadratic in sequence length

The d term:

  • Model dimension (typically 768-12288)
  • Vector operations scale with dimension

Multi-Head Attention:

  • Still O(n²d) overall
  • Divides d into h heads (d/h each)
  • h heads × O(n²d/h) = O(n²d)
  • Can parallelize across heads

Linear Attention: O(nd²)

  • Reformulates: (QK^T)V = Q(K^T V)
  • Compute K^T V first: O(nd²)
  • Then Q @ (K^T V): O(nd²)
  • Faster when n >> d

Sparse Attention: O(n√n d) or O(n log n d)

  • Only attends to subset of tokens
  • Local window + global tokens
  • Reduces n² to n√n or n log n

Flash Attention: O(n²d) time, O(n) space

  • Same computation, block-wise
  • Doesn’t store full attention matrix
  • Memory efficient

See 05_attention_mechanisms/attention_complexity.md for complete analysis!

Getting the accounting exactly right.

The derivation above is correct but leaves out the term that decides everything in practice. A full attention layer has two kinds of cost:

  • The projections — computing , , and the output projection — are four matmuls of , so .
  • The attention itself then — is .

Total . Which term dominates is just the ratio , and that single observation explains a lot of otherwise confusing behaviour:

dominant
512projections
1,024projections
4,096equal
16,384attention (4×)
131,072attention (32×)

(Computed with .) The crossover sits exactly at . So at the sequence lengths typical of BERT-era training, the quadratic term was not the bottleneck at all and the feed-forward and projection matmuls dominated — which is why “attention is quadratic and therefore the problem” was somewhat overstated for years, and why it became genuinely urgent only once contexts pushed past ten thousand tokens.

FlashAttention deserves a sharper explanation than the answer above gives. Saying “same computation, block-wise, memory efficient” undersells it. The real point is that attention is memory-bandwidth-bound, not compute-bound: the score matrix must be written to high-bandwidth memory, read back for the softmax, written again, and read again for the multiply by . At that matrix alone is about 34 GB in fp16 per head-batch — it does not fit anywhere sensible. FlashAttention tiles the computation so that blocks of Q, K and V are loaded into fast on-chip SRAM, computes the partial attention there, and combines results using the online softmax trick — an incremental formulation that maintains a running maximum and running sum so the normalization can be corrected as new blocks arrive, without ever materializing a full row. Memory drops from to and, because far less data crosses the memory bus, wall-clock time improves substantially even though the FLOP count is unchanged. It is exact, not an approximation — which is why it is now the default everywhere and why the approximate-attention literature lost much of its motivation.

A caveat on linear attention. via the associativity is real, but it requires replacing the softmax with a kernel feature map , since softmax does not factorize — you compute . That changes what the model computes, and quality has historically lagged full attention, particularly on tasks needing precise retrieval from context. It also flips the constant: with is large, so linear attention only wins for genuinely long sequences. The modern descendants of this idea — state-space models and linear-recurrent architectures — are more competitive, and hybrid designs that interleave a few full-attention layers among many linear ones are a common compromise. This area is moving quickly; verify the current state before asserting what is standard.

The generation-time picture is different, and is worth adding. All the above concerns training or prefill. With a KV cache during decode (Q88), generating one token costs attention against the cached history plus of projections — linear in context, not quadratic. The binding constraint at decode time is memory: the KV cache is and it is what limits concurrency on a serving GPU. So “attention is quadratic” is a training and prefill statement; at decode the problem is the cache. Distinguishing the two is the detail that marks someone who has profiled a real system.

Follow-up: If attention were free, would context be unlimited? No. Two other limits bind. The KV cache still grows linearly and still exhausts memory. And model quality over long context does not scale with the window — the lost-in-the-middle effect (Q59) means retrieval accuracy from deep context degrades regardless of how cheaply you computed the attention. Longer windows are an infrastructure achievement that outruns the modelling.

Why the interviewer asks this. Complexity questions separate people who have read the paper from people who have profiled a model, and the tell is whether you mention the term and the memory bandwidth.

Saying it out loud. Attention itself is n-squared times d, because you build an n-by-n score matrix and each entry is a d-dimensional dot product. But the projections — Q, K, V and the output — are n times d-squared, and which one dominates depends on whether n is bigger or smaller than d. At a sequence length of 512 with a model width of 4096, the projections actually dominate; the crossover is right at n equals d. That’s why the quadratic term only became urgent once contexts got long. The other thing I’d say is that attention is memory-bandwidth-bound, not compute-bound — you write the whole score matrix out to memory and read it back. That’s what FlashAttention fixes: it tiles the computation into on-chip memory and uses an online softmax so it never materializes the full matrix. Same math, exact, but memory goes from n-squared to n and it’s much faster in wall-clock terms.


See 04_transformers/gpt_complete.py for complete GPT implementation! See 04_transformers/gpt_training_decoding.md for training and decoding details! See 05_attention_mechanisms/attention_complexity.md for complexity analysis!


Prompt Tuning and Prefix Tuning

Q90: What is prompt tuning? How does it work?

In 30 seconds. “Prompt tuning freezes the whole model and learns a handful of fake token embeddings glued onto the front of every input. They’re not real words — just vectors in embedding space that gradient descent found useful for the task. You train maybe fifteen thousand numbers instead of a hundred and twenty million, so each task is a tiny file you swap in. The catch is that it only really matches full fine-tuning once the base model is big, roughly ten billion parameters and up.”

The short version.

  • What’s trainable: soft-prompt embeddings — nothing else.
  • Typical size: 20–100 virtual tokens; floats, about 0.01% of the model.
  • Mechanism: prepend to the input embeddings, forward through the frozen model, backprop into only.
  • Learning rate: 0.1–0.5, roughly a thousand times higher than full fine-tuning.
  • Cost: the prompt occupies real context-window positions at every forward pass.

Why it works.

Writing a good prompt by hand is searching a discrete space of words for something that steers the model. Prompt tuning does the same search in the continuous space the words live in — so it can land between words, on vectors no string maps to. Gradient descent is much better at that search than you are, which is the entire pitch.

The math, and what it buys you.

Answer:

Prompt Tuning:

  • Parameter-efficient fine-tuning method
  • Adds trainable “soft prompts” (continuous embeddings) to input
  • Keeps entire pre-trained model frozen
  • Only trains prompt embeddings (typically 20-100 tokens)

How It Works:

  1. Prepend trainable prompt embeddings to input
  2. Pass [prompt; input] through frozen model
  3. Only update prompt embeddings during training
  4. Prompt learns to encode task-specific information

Mathematical Formulation:

E_input = Embedding(x)  # Input embeddings
P = [p₁, ..., pₚ]  # Trainable prompt (p tokens)
E_combined = [P; E_input]  # Concatenate
output = Model_θ(E_combined)  # Model frozen
# Only P is updated: P ← P - α∇P

Parameters:

  • Trainable: p × d_model (e.g., 20 × 768 = 15,360)
  • Efficiency: 0.01% of model parameters
  • Storage: Only prompt embeddings per task

Advantages:

  • Extremely parameter-efficient
  • Simple implementation
  • Fast training
  • Preserves pre-trained knowledge
  • Enables multi-task deployment

Working Through the Parameter Count:

The trainable size is just , where is the number of soft-prompt tokens and is the hidden width. For a GPT-2 small backbone, floats. Against 124M backbone parameters that is . Note what this number does not depend on: the number of layers, or the vocabulary size. Prompt tuning is the only PEFT method whose cost is flat in model depth, which is why the per-task storage stays in the tens of kilobytes even for a 70B model (a 70B model with and 20 tokens is 163,840 floats, roughly 320 KB in fp16).

Why the Learning Rate Is So Large:

Soft prompts are typically trained with learning rates around to — a thousand times higher than the used for full fine-tuning. The reason is that gradients reach the prompt only through the frozen stack, so the signal arriving at is small, and there is no risk of damaging pretrained weights because there are none in the optimizer. A candidate who quotes lr=3e-5 for prompt tuning usually has not actually run it.

The Scale Caveat:

The original result (Lester et al., 2021) is that prompt tuning only closes the gap with full fine-tuning at large scale — around 10B parameters and up. On a sub-1B model it typically underperforms LoRA noticeably. So “prompt tuning is as good as fine-tuning” is a claim with a size condition attached, and interviewers like to check whether you know it.

Follow-up: Does the soft prompt consume context window? Yes. The prompt tokens occupy positions in the sequence, so a 20-token prompt costs 20 tokens of usable context at every forward pass and adds extra attention work. LoRA has no such cost.

Why the interviewer asks this. It separates people who have read the PEFT papers from people who have only used the word “prompt” to mean text they typed into a chat box.

Saying it out loud. Prompt tuning freezes the whole model and learns a handful of fake token embeddings that you glue onto the front of every input. They’re not real words — they’re just vectors in embedding space that gradient descent found useful for the task. You end up training something like fifteen thousand numbers instead of a hundred and twenty million, so each task is a tiny file you can swap in. The catch is that it really only matches full fine-tuning once the base model is big, roughly ten billion parameters and up.


Q91: What is prefix tuning? How does it differ from prompt tuning?

In 30 seconds. “Prompt tuning only touches the input embeddings, so the deeper layers have to be steered indirectly. Prefix tuning pushes learned vectors into the keys and values at every layer, so it can nudge the computation all the way up the stack. You pay about times more parameters — twenty-four times for a twelve-layer model — and get much closer to full fine-tuning on hard tasks. The neat asymmetry: the prefix is attended to, but it never attends to anything itself.”

The short version.

Prompt tuningPrefix tuning
Whereinput embedding layer onlyevery transformer layer
Whatprompt embeddingsprefix keys and values
Params
Example (12L, 20 tok, 768d)15,360368,640
Expressivenessgood on simple tasksoften matches full fine-tuning
Training stabilitystraightforwardneeds an MLP reparameterization

Why it works.

Prompt tuning writes a note at the top of the page and hopes the whole document is read in that light. Prefix tuning slips a note into every chapter. Because a Transformer’s layers compute progressively more abstract features, being able to intervene at layer 9 as well as layer 0 gives you control the input alone cannot reach — which is exactly why the extra factor buys real quality rather than just more parameters.

The math, and what it buys you.

Answer:

Prefix Tuning:

  • Similar to prompt tuning but adds parameters at every layer
  • Adds trainable “prefix” key-value pairs at each transformer layer
  • More expressive than prompt tuning
  • Still parameter-efficient

Key Differences:

1. Where Parameters Are Added:

  • Prompt tuning: Only at input layer
  • Prefix tuning: At every transformer layer
  • Impact: Prefix influences model at multiple levels

2. What’s Added:

  • Prompt tuning: Prompt embeddings (input)
  • Prefix tuning: Prefix keys and values (attention)
  • Impact: Prefix directly modifies attention computation

3. Parameters:

  • Prompt tuning: p × d_model
  • Prefix tuning: L × p × 2d_model (for K and V)
  • Example: 12 layers, 20 tokens, 768 dim
    • Prompt: 15,360 parameters
    • Prefix: ~368,640 parameters
    • Still much less than full model

4. Performance:

  • Prompt tuning: Good for simple tasks
  • Prefix tuning: Often matches full fine-tuning
  • Trade-off: More parameters for better performance

Mathematical Formulation:

At each layer l:
K_l = [P_l^K; K_l]  # Add prefix keys
V_l = [P_l^V; V_l]  # Add prefix values
Q_l unchanged
Attention_l = softmax(Q_l K_l^T) V_l

Checking the Parameter Arithmetic:

The formula with layers, prefix tokens and gives . The factor of 2 is because you store one prefix for keys and one for values. That is 24 times more than prompt tuning’s 15,360 for the same 20 tokens — the multiplier is exactly .

What “Prepending to K and V” Actually Means:

At layer , ordinary self-attention over a sequence of length computes , , . Prefix tuning concatenates learned rows on top of and only, giving and , while stays at rows. The attention matrix becomes instead of : every real token gets extra things it may attend to, but the prefix positions themselves never produce outputs. That asymmetry is the whole trick — the prefix is pure memory, never a query.

Why the Reparameterization Exists:

Training the prefix matrices directly is unstable; the loss is very sensitive to the learning rate and often diverges. The paper’s fix is to learn a smaller matrix and pass it through an MLP to produce the real prefixes, then throw the MLP away after training and keep only the expanded . So the deployed artifact is still just numbers; the extra machinery is a training-time crutch.

Follow-up: Is prefix tuning the same as P-tuning v2? Effectively yes for the mechanism — P-tuning v2 is deep prompt tuning that injects trainable key/value prefixes at every layer, which is prefix tuning without the reparameterization MLP, retuned for classification and sequence labelling.

Saying it out loud. Prompt tuning only touches the input embeddings, so the model’s deeper layers have to be steered indirectly. Prefix tuning pushes learned vectors into the keys and values at every single layer, so it can nudge the computation all the way up the stack. You pay about two-L times more parameters — twenty-four times for a twelve-layer model — but you get much closer to full fine-tuning on hard tasks. The prefix is attended to, but it never attends to anything itself.


Q92: Compare prompt tuning, prefix tuning, LoRA, and full fine-tuning.

In 30 seconds. “All four do the same job — adapt a pretrained model to a task — but they trade off differently. Full fine-tuning is best and most expensive. LoRA gets within noise of it for about a tenth of a percent of the parameters, and crucially it merges back into the weights, so inference costs nothing extra. Prefix tuning is close behind; prompt tuning is cheapest and works best on very large models. I’d default to LoRA and only move off it for a specific reason.”

The short version.

MethodTrainableExample (GPT-2, 125M)QualityExtra inference cost
Full fine-tuning100%125Mbestnone, but a full model per task
LoRA0.1–1%125K–1.25Mnear-bestzero — merges into
Prefix tuning~0.3%~368Kvery goodlonger sequence, every token
Prompt tuning~0.01%~15Kgood at scalelonger sequence, every token

Ladder: start with prompt tuning if you need maximum efficiency, move to prefix tuning for harder tasks, use LoRA for the best balance, and full fine-tune only when you must.

Why it works.

The unifying idea is that adapting a pretrained model does not require moving all of it — the task-specific change is low-dimensional, so a small number of well-placed parameters can express it. Where the methods differ is where they place those parameters: in the input (prompt), in the attention memory at every layer (prefix), or additively inside the weight matrices themselves (LoRA). Only the last one lives in the same space as the original weights, which is why only LoRA can be folded away at inference.

The math, and what it buys you.

Answer:

Parameter Efficiency:

MethodParametersExample (GPT-2)Efficiency
Full Fine-tuning100%125M1x
LoRA0.1-1%125K-1.25M100-1000x
Prefix Tuning0.3%~368K~340x
Prompt Tuning0.01%~15K~8000x

Performance:

Full Fine-tuning:

  • Best performance
  • Risk of catastrophic forgetting
  • Requires most resources

LoRA:

  • Near full fine-tuning performance
  • Best balance of efficiency and performance
  • Most popular in practice

Prefix Tuning:

  • Very good performance (often matches full fine-tuning)
  • More expressive than prompt tuning
  • Good for complex tasks

Prompt Tuning:

  • Good performance
  • Sufficient for many tasks
  • Maximum efficiency

Use Cases:

  • Full Fine-tuning: Maximum performance, single task
  • LoRA: Best balance, most common
  • Prefix Tuning: Complex tasks, good performance
  • Prompt Tuning: Simple tasks, maximum efficiency

Recommendation:

  • Start with prompt tuning (simplest)
  • If insufficient, try prefix tuning
  • For best balance, use LoRA
  • Full fine-tuning only if needed

Reading the Table:

The middle column is the same GPT-2-sized model (roughly 125M parameters) under each method, so the numbers are directly comparable. Full fine-tuning updates all 125M. LoRA at rank on the query and value projections of layers costs parameters — for , , that is , about . Prefix tuning’s 368K and prompt tuning’s 15K come from the two previous questions. The “Efficiency” column is just divided by the trainable count, so it is a storage-and-optimizer-state ratio, not a speed ratio.

The Distinction the Table Hides:

Trainable-parameter count is not the same as training cost. All four methods still run a full forward and backward pass through the whole network — you save optimizer state and gradient memory, not FLOPs. Adam keeps two moments per trainable parameter, so full fine-tuning of a 7B model needs roughly GB just for fp32 weights plus moments, while LoRA at 0.1% needs about 84 MB for the same states. That memory difference, not compute, is why PEFT lets you fine-tune on one GPU.

The One Property Only LoRA Has:

LoRA’s update is , a plain additive change to a weight matrix, so it can be merged into the base weights after training and adds exactly zero inference latency. Prompt and prefix tuning both lengthen the sequence, so they cost extra attention work at every token forever. If the interviewer asks “which of these is free at inference time,” the answer is LoRA and only LoRA.

Follow-up: Which would you actually reach for in 2026? LoRA or QLoRA, essentially always — it dominates on the quality-per-parameter curve, merges away at inference, and has the most mature tooling. Prompt and prefix tuning are worth knowing mainly because they are asked about and because prompt tuning’s flat-in-depth cost is genuinely appealing for serving hundreds of tasks off one frozen backbone.

Why the interviewer asks this. They want to see whether you can rank methods on more than one axis at once — parameters, quality, and inference cost do not agree, and the interesting answer lives in the disagreement.

Saying it out loud. All four of these do the same job — adapt a pretrained model to a task — but they trade off differently. Full fine-tuning is best and most expensive. LoRA gets you within noise of it for about a tenth of a percent of the parameters, and crucially you can merge it back into the weights so inference costs nothing extra. Prefix tuning is close behind, prompt tuning is the cheapest and works best on very large models. In practice I’d default to LoRA and only move off it for a specific reason.


Q93: How do you initialize prompt/prefix embeddings?

In 30 seconds. “A soft prompt lives in the same space as real token embeddings, so initializing it with pure Gaussian noise hands the frozen model vectors that look like nothing it has ever seen. Copying the embeddings of actual vocabulary tokens — ideally words related to the task, like the label words for a classification problem — starts you somewhere the model already understands. It matters a lot on small models and much less on huge ones.”

The short version.

StrategyHowWhen
Randombaseline; fine on very large models
Vocabulary-basedcopy embeddings of frequent real tokensdefault for prompt tuning
Task-specificcopy embeddings of label/verbalizer wordsclassification with natural label words
Reparameterizationlearn small, project up with an MLPdefault for prefix tuning (stability)

Why it works.

The frozen model’s embedding table occupies a specific, anisotropic region of the space — a particular typical norm and direction structure. Random vectors land outside it, so the model treats them as out-of-distribution noise and gradients stay weak until they drift back in. Starting from real embeddings starts you on the manifold. It is the difference between joining a conversation in a language everyone speaks and shouting a made-up one until people work out what you mean.

The math, and what it buys you.

Answer:

Initialization Strategies:

1. Random Initialization:

P ~ N(0, 0.02²)  # Small random values
  • Simple, unbiased
  • May require more training

2. Vocabulary-Based:

Sample random tokens from vocabulary
Use their embeddings as initial prompt
  • Starts with semantic information
  • Often works better than random

3. Task-Specific:

Use embeddings from task-related tokens
E.g., sentiment: "sentiment", "positive", "negative"
  • Better starting point
  • Faster convergence

4. Reparameterization (Prefix):

Learn in smaller space (d_model/2)
Project up to full dimension
  • More stable training
  • Used in prefix tuning

Best Practices:

  • Prompt tuning: Vocabulary-based initialization
  • Prefix tuning: Reparameterization + random
  • Experiment: Try different strategies
  • Use domain knowledge: When available

Why Initialization Matters More Here Than Usual:

In full fine-tuning, initialization barely matters because you start from pretrained weights and take small steps. In prompt tuning you are creating brand-new vectors in a space the model has strong opinions about — the embedding matrix occupies a particular region, with a typical norm and a particular anisotropic shape. Random vectors can land far outside that region, so the frozen model treats them as out-of-distribution garbage and gradients are weak until they wander back. Sampling real vocabulary embeddings starts you inside the manifold the model already understands.

Concretely, Vocabulary-Based Initialization:

Take the most frequent tokens in the vocabulary (or tokens from the task’s label words), look up their rows in the frozen embedding table, and copy those as the initial . Lester et al. found that on smaller models this beats random initialization by several points; on very large models the gap closes, because a big model can recover from a bad start.

Class-Label Initialization for Classification:

For a sentiment task with labels “positive” and “negative”, initializing some prompt slots with the embeddings of those exact words gives the model a head start on the output distribution. This is closer to converting a hand-written discrete prompt into a soft one, and it is the strongest option when the task has natural verbalizer words.

Saying it out loud. The thing to remember is that a soft prompt lives in the same space as real token embeddings, so if you initialize it with pure Gaussian noise you’re handing the frozen model vectors that look like nothing it’s ever seen. Copying embeddings of actual vocabulary tokens — ideally words related to the task, like the label words for a classification problem — starts you somewhere the model already understands. It matters a lot on small models and less on huge ones.


Q94: What is the optimal prompt/prefix length?

In 30 seconds. “Prompt length is the capacity dial — same idea as rank in LoRA. You get most of the benefit by about twenty tokens and it flattens after that, so people land between twenty and fifty. The reason not to crank it to a hundred is that every prompt token is a real position in the sequence: it costs context window and attention compute on every forward pass. I’d sweep a few values on validation and take the smallest one within noise of the best.”

The short version.

  • Prompt tuning: 20–100 tokens, commonly 20–50.
  • Prefix tuning: 10–50 tokens, commonly 10–20 — each token carries times more parameters.
  • Longer for: complex tasks, large datasets, large models.
  • Shorter for: simple tasks, small datasets, tight context budgets.
  • Curve: steep to ~20 tokens, then a plateau.

Why it works.

The soft prompt is the only thing that can encode the task — the rest of the model is frozen. So its length is literally the model’s task capacity, and the curve behaves like every capacity curve: fast gains while the parameterization is the bottleneck, then a plateau once something else is. The cost side is what makes the plateau decisive, because unlike LoRA rank, prompt length is charged against your context window forever.

The math, and what it buys you.

Answer:

Typical Ranges:

  • Prompt tuning: 20-100 tokens (commonly 20-50)
  • Prefix tuning: 10-50 tokens (commonly 10-20 per layer)

Selection Factors:

1. Task Complexity:

  • Simple tasks: 20 tokens sufficient
  • Complex tasks: 50-100 tokens needed
  • Rule: More complex → longer prompt/prefix

2. Dataset Size:

  • Large datasets: Can support longer prompts
  • Small datasets: Shorter prompts (avoid overfitting)

3. Model Size:

  • Larger models: Can utilize longer prompts
  • Smaller models: Shorter prompts sufficient

Selection Process:

  1. Start with moderate length (20-30 tokens)
  2. Try different lengths: [10, 20, 50, 100]
  3. Evaluate on validation set
  4. Choose best performing length

Empirical Finding:

  • Performance improves with length up to a point
  • Then plateaus (diminishing returns)
  • Sweet spot: 20-50 tokens for most tasks

What Length Actually Buys You:

The soft prompt is the entire capacity of the method — it is the only thing that can encode the task. So length is the capacity knob, exactly like rank in LoRA. With , a 20-token prompt has 15,360 degrees of freedom and a 100-token prompt has 76,800. The empirical curve is steep from 1 to about 20 tokens, then close to flat: Lester et al. report that going beyond 20 gives little on a 10B+ model, while smaller models keep improving out to 100.

The Costs of Going Long:

Every prompt token is a real sequence position. With prompt length and input length , the attention cost goes from to , and the prompt eats tokens of your context budget on every single example. At on a 512-token context you have given up nearly 20% of the window before the user has said anything. That is the practical ceiling, not overfitting.

How to Actually Pick It:

Sweep on a validation set, plot score against , and take the smallest within noise of the best. This is a one-dimensional search over a cheap-to-train model, so it is one of the few hyperparameter sweeps that is genuinely affordable.

Follow-up: Does prefix tuning need the same length? No, it can be shorter — 10 to 20 is typical — because it gets times more parameters out of each token. Capacity per prefix token is much higher, so you need fewer of them.

Saying it out loud. Prompt length is the capacity dial, same idea as rank in LoRA. You get most of the benefit by about twenty tokens and it flattens out after that, so people usually land somewhere between twenty and fifty. The reason not to just crank it to a hundred is that every prompt token is a real position in the sequence — it costs you context window and attention compute on every forward pass. I’d sweep a few values on validation and take the smallest one that’s within noise of the best.


Q95: Implement prompt tuning from scratch. Show the key code.

In 30 seconds. “The whole implementation is about fifteen lines. Freeze every backbone parameter, create one trainable tensor shaped prompt-length by hidden-size, embed the input normally, and concatenate the prompt in front along the sequence dimension. Then feed it in as inputs_embeds instead of input_ids, because these vectors don’t correspond to any real token. The two bugs everyone hits are forgetting to slice the prompt positions off the logits before computing loss, and forgetting to pad the attention mask.”

The short version.

  • Set requires_grad = False on all backbone parameters.
  • One nn.Parameter of shape (prompt_length, d_model), init scale 0.02.
  • wte(input_ids)(B, S, D); expand the prompt to (B, P, D); cat on dim=1.
  • Pass as inputs_embeds, not input_ids.
  • Optimizer sees only the prompt tensor, at lr=0.3.
  • Slice logits[:, P:, :] before the loss. Left-pad the attention mask by P.

Why it works.

The frozen model has no idea it is being steered. From its point of view it received a sequence of embeddings and processed them normally — the first twenty just happen to be vectors that no tokenizer would ever produce. Gradients flow back through the whole frozen stack and land only on those twenty vectors, so training is a search for the twenty embeddings that make the untouched model behave the way you want.

The math, and what it buys you.

Answer:

Key Implementation:

class PromptTuning(nn.Module):
    def __init__(self, model, prompt_length=20):
        super().__init__()
        self.model = model
        self.prompt_length = prompt_length
        self.d_model = model.config.n_embd
        
        # Freeze model
        for param in model.parameters():
            param.requires_grad = False
        
        # Trainable prompt embeddings
        self.prompt_embeddings = nn.Parameter(
            torch.randn(prompt_length, self.d_model) * 0.02
        )
    
    def forward(self, input_ids):
        batch_size = input_ids.size(0)
        
        # Input embeddings
        input_emb = self.model.transformer.wte(input_ids)
        
        # Expand prompt for batch
        prompt = self.prompt_embeddings.unsqueeze(0).expand(batch_size, -1, -1)
        
        # Concatenate: [prompt; input]
        combined = torch.cat([prompt, input_emb], dim=1)
        
        # Forward through frozen model
        outputs = self.model.transformer(inputs_embeds=combined)
        logits = self.model.lm_head(outputs.last_hidden_state)
        
        return logits

# Training
optimizer = torch.optim.Adam([prompt_model.prompt_embeddings], lr=0.3)
# Only prompt_embeddings are updated

Key Points:

  • Freeze all model parameters
  • Only prompt_embeddings requires gradients
  • Simple concatenation at input
  • Extremely parameter-efficient

See 25_adapters_lora/prompt_prefix_code.py for complete implementation!

Walking Through the Code:

The constructor does three things. It records d_model from the backbone config so the prompt vectors have the right width. It sets requires_grad = False on every backbone parameter, which is what makes the model frozen — without this line the optimizer would still only see the prompt, but the backward pass would waste time computing and storing gradients for 124M weights. And it registers prompt_embeddings as an nn.Parameter of shape (prompt_length, d_model), scaled by 0.02 to match GPT-2’s embedding initialization scale.

In forward, wte is GPT-2’s word token embedding table, so input_emb has shape (batch, seq_len, d_model). The prompt is (prompt_length, d_model); unsqueeze(0) makes it (1, prompt_length, d_model) and expand broadcasts it to (batch, prompt_length, d_model) without copying memory. The torch.cat along dim=1 produces (batch, prompt_length + seq_len, d_model), which is handed to the transformer as inputs_embeds rather than input_ids — that is the only way to feed vectors that do not correspond to any real token.

Two Things This Snippet Leaves Out (worth saying in an interview):

First, the output logits now cover prompt_length + seq_len positions, so when you compute the loss you must slice off the first prompt_length positions before comparing to labels — otherwise you are training the model to predict your prompt. Second, if you pass an attention_mask for padding you must left-pad it with prompt_length ones, or the model will mask out its own prompt. Both are the classic bugs in hand-rolled prompt tuning.

Verified Shape Arithmetic:

import torch
B, S, P, D = 4, 16, 20, 768
input_emb = torch.zeros(B, S, D)
prompt = torch.zeros(P, D).unsqueeze(0).expand(B, -1, -1)
combined = torch.cat([prompt, input_emb], dim=1)
print(combined.shape)          # torch.Size([4, 36, 768])
print(P * D)                   # 15360 trainable parameters
logits = torch.zeros(B, P + S, 50257)
print(logits[:, P:, :].shape)  # torch.Size([4, 16, 50257]) -- slice before loss

Follow-up: Why lr=0.3 in the optimizer line? Because the prompt is the only trainable tensor and its gradients, arriving through a deep frozen stack, are tiny. Prompt tuning is routinely run at learning rates between and ; at it would essentially not move.

Why the interviewer asks this. Asking for code exposes whether you have actually run the method — the loss-slicing and attention-mask details only surface when something has gone wrong for you once.

Saying it out loud. The whole implementation is about fifteen lines. You freeze every backbone parameter, create one trainable tensor shaped prompt-length by hidden-size, embed the input normally, and concatenate the prompt in front along the sequence dimension. Then you feed it in as inputs_embeds instead of input_ids, because these vectors don’t correspond to any real token. The two bugs everyone hits are forgetting to slice the prompt positions off the logits before computing loss, and forgetting to pad the attention mask.


See 25_adapters_lora/prompt_prefix_tuning.md for detailed theory! See 25_adapters_lora/prompt_prefix_code.py for complete code! See 25_adapters_lora/prompt_prefix_qa.md for comprehensive Q&A!


Diffusion Models

Q96: What are diffusion models? How do they work?

In 30 seconds. “A diffusion model learns to undo noise. You take real data and gradually corrupt it with Gaussian noise until it’s pure static — and that direction is fixed, no learning involved. Then you train a network to look at a noisy sample plus the timestep and guess what noise was added. To generate, start from static and denoise step by step. The clever bit is that you can jump straight to any noise level in closed form, so training one example is one forward pass, not a thousand.”

The short version.

  • Forward process (fixed, no learning): — add noise until it is static.
  • Reverse process (learned): — a network removes a little noise at a time.
  • Training target: predict the noise, .
  • Generation: sample and iterate .
  • Why it’s learnable: many easy steps instead of one impossibly hard one.

Why it works.

Generating a photograph in one shot means sampling from a distribution nobody can write down. Diffusion replaces that with a thousand tiny problems — “this image is slightly noisy, clean it up a bit” — each of which is nearly a local, well-behaved regression. It is the difference between sculpting a statue from a block and being handed something 99% finished and asked to remove one chip. Do that a thousand times and you have carved the statue.

The math, and what it buys you.

Answer:

Diffusion Models:

  • Generative models that learn to reverse a gradual noising process
  • Work by iteratively removing noise from data, starting from pure noise
  • State-of-the-art results in image generation (DALL-E, Stable Diffusion)

How They Work:

1. Forward Process (Fixed):

  • Gradually add Gaussian noise to data
  • q(x_t | x_{t-1}) = N(x_t; √(1-β_t)x_{t-1}, β_t I)
  • After T steps, data becomes pure noise

2. Reverse Process (Learned):

  • Learn to remove noise step by step
  • p_θ(x_{t-1} | x_t) = N(x_{t-1}; μ_θ(x_t, t), Σ_θ(x_t, t))
  • Neural network predicts how to denoise

3. Training:

  • Predict the noise that was added
  • Loss: L = E[||ε - ε_θ(x_t, t)||²]

4. Generation:

  • Start from pure noise x_T ~ N(0, I)
  • Iteratively apply reverse process: x_T → x_{T-1} → … → x_0

Key Insight:

  • Break down complex generation into many simple denoising steps
  • Each step only removes small amount of noise
  • Much easier to learn than generating directly

The One Equation That Makes Training Cheap:

Written literally, the forward process is a chain: to get you would apply the noising step times. But because each step is Gaussian and the composition of Gaussians is Gaussian, the chain collapses into a closed form. Define and . Then

which you sample as with . This is the single most important fact about diffusion training: you can jump to any timestep in one line, so a training step never simulates the chain. Without it, training would cost per example.

Worked Numbers:

With a linear schedule from to over steps, , so — the original image contributes essentially nothing and is indistinguishable from pure noise. At , , so is still about 95% signal by amplitude. That spread is why the model must be told : the same network handles “barely noisy” and “pure static,” and only the timestep embedding tells it which regime it is in.

Why Predict Noise Instead of the Image:

Predicting rather than makes the target have unit variance at every timestep, so the loss is comparably scaled across and the network does not have to learn a -dependent output magnitude. It is a variance-reduction trick, and it is why the standard objective is rather than a reconstruction loss.

Follow-up: Why so many steps? Each reverse step is only approximately Gaussian, and the approximation is good only when is small. Many small steps keep the Gaussian assumption valid. Deterministic samplers like DDIM exploit the same trained model with 20-50 steps by taking a non-Markovian path, which is how production image models sample fast.

Why the interviewer asks this. Diffusion is the one modern generative family whose training objective looks nothing like next-token prediction, so it is a clean test of whether you understand a model you did not memorize.

Saying it out loud. A diffusion model learns to undo noise. You take real data and gradually corrupt it with Gaussian noise until, after a thousand steps, it’s pure static — and that direction is fixed, no learning involved. Then you train a network to look at a noisy sample plus the timestep and guess what noise was added. To generate, you start from static and denoise step by step. The clever bit is that you can jump straight to any noise level in closed form, so training one example is one forward pass, not a thousand.


Q97: How do you train a diffusion model?

In 30 seconds. “Training a diffusion model is plain regression, not anything exotic. For each example you pick a random timestep, sample noise, jump straight to the noisy version in one closed-form step, and ask the network to predict the noise you just added — mean squared error, done. Nothing loops over the thousand diffusion steps during training; that only happens at sampling time.”

The short version.

  • Precompute the variance schedule and its cumulative products , once, up front.
  • Per example: draw , draw , draw .
  • Build in one shot from the closed form — no iteration over timesteps.
  • Loss is . That’s it.
  • Cosine schedule over linear; EMA weights for sampling; feed into every block.

Why it works.

Steps (a) through (d) below are just “make one training example.” You draw a clean sample , draw a random timestep uniformly from — this is important, each example in a batch gets a different noise level, so the batch covers the whole schedule — draw fresh noise , and form with the closed-form jump from the previous question. Steps (e) through (g) are an ordinary regression: the network sees and must output , and you take an MSE gradient step. Nothing here iterates over ; the loop body is in the number of diffusion steps.

The math, and what it buys you.

1. Setup:

  • Define variance schedule β_t (linear or cosine)
  • Precompute α_t, ᾱ_t for efficiency

2. Training Loop:

For each batch:
  a. Sample data: x_0 ~ q(x_0)
  b. Sample timestep: t ~ Uniform({1, 2, ..., T})
  c. Sample noise: ε ~ N(0, I)
  d. Create noisy data: x_t = √(ᾱ_t)x_0 + √(1-ᾱ_t)ε
  e. Predict noise: ε_pred = ε_θ(x_t, t)
  f. Compute loss: L = ||ε - ε_pred||²
  g. Update: θ ← θ - α∇_θ L

Variance Schedule:

  • Linear: β_t = (β_max - β_min) * (t/T) + β_min
  • Cosine: ᾱ_t = cos²(π/2 * (t/T)) (often better)

The payoff of the closed form in step (d) is that training cost is independent of — you can train a 1000-step model as cheaply as a 50-step one, because you never simulate the chain.

Why the Cosine Schedule Usually Wins:

The linear schedule destroys information too early: drops fast, so a large fraction of the timesteps are spent on samples that are already essentially pure noise and carry no learning signal. Nichol and Dhariwal’s cosine schedule keeps near 1 for longer and decays smoothly near the end, spending more of the budget on the noise levels where the model actually has something to learn. The practical effect is better sample quality at the same , especially at low resolution.

Good and bad.

What makes training go well:

  • Learning rate: 1e-4 to 1e-3
  • Use learning rate scheduling (cosine annealing)
  • Gradient clipping (norm = 1.0)
  • Monitor loss and generate samples during training

Two Practical Details the Answer Omits:

Use an exponential moving average of the weights for sampling — EMA with decay around is close to universal in diffusion training and the difference in sample quality is large, not marginal. And condition on with sinusoidal position embeddings passed into every residual block (usually via FiLM-style scale-and-shift), not just concatenated at the input; the network needs the timestep at every depth.

Follow-up: How do you make it conditional, e.g. text-to-image? Add the conditioning signal to the network and train , dropping at random on maybe 10% of examples. At sampling time you extrapolate between the conditional and unconditional prediction — classifier-free guidance, — which trades diversity for prompt adherence as grows.

Saying it out loud. Training is surprisingly plain. For each example you pick a random timestep, sample some noise, use the closed-form formula to build the noisy version in one shot, and ask the network to predict the noise you just added. It’s mean-squared error regression. The parts people forget are that the timestep has to be fed into every block, not just the input, and that you should keep an EMA copy of the weights for sampling — that one alone makes a visible difference.


Q98: What are discrete diffusion models? How do they work for NLP?

In 30 seconds. “You can’t add Gaussian noise to a word, so discrete diffusion replaces ‘add noise’ with ‘randomly substitute tokens.’ The version that works replaces them with a [MASK] that can never change back, so after enough steps the whole sequence is masked — which makes discrete diffusion essentially BERT-style masked prediction with a randomized, annealed mask ratio, run repeatedly at generation time.”

The short version.

Continuous diffusion (images)Discrete diffusion (text)
CorruptionAdd Gaussian noiseRandom token substitution via matrix
Forward stepGaussian transition kernelq(x_t | x_{t-1}) = Categorical(x_t; Q_t x_{t-1})
Reverse stepPredict the noise Predict the original token: p_θ(x_{t-1} | x_t) = Categorical(x_{t-1}; p_θ(x_t, t))
End state at Pure noiseAll [MASK] (absorbing) or uniform random tokens

Two corruption schemes:

  • Absorbing state: each token jumps to [MASK] with probability ; [MASK] never leaves. This is the one that works.
  • Uniform transition: tokens can become any other token uniformly.

1. Absorbing State:

  • Have special [MASK] token
  • At each step, tokens transition to [MASK] with probability β_t
  • After T steps, all tokens become [MASK]

2. Uniform Transition:

  • Tokens can transition to any other token uniformly

The Challenge:

  • Standard diffusion works on continuous data (images)
  • Text is discrete (tokens), need adaptation

Why it works.

Instead of Gaussian noise, use transition matrix:

q(x_t | x_{t-1}) = Categorical(x_t; Q_t x_{t-1})

Learn to predict original token:

p_θ(x_{t-1} | x_t) = Categorical(x_{t-1}; p_θ(x_t, t))

For continuous data, “add noise” means adding a Gaussian. For tokens there is no meaningful way to add to the word “cat,” so corruption has to be a random substitution. Represent a token as a one-hot vector ; then is a column-stochastic matrix and is the distribution over what the token becomes at step . The absorbing-state variant sets so that a token stays put with probability and jumps to [MASK] with probability ; [MASK] never leaves, which is why it is called absorbing.

Why Uniform Transitions Are Worse in Practice:

With uniform corruption, a token can become any other token, so at inference the model cannot tell which positions are corrupted — it must decide both what is wrong and how to fix it. The absorbing variant marks corruption explicitly with [MASK], which is a much easier learning problem and is what most working discrete-diffusion text models use.

The math, and what it buys you.

Worked Example:

Take a 10-token sentence and a schedule where the cumulative mask probability reaches 1 at . At with , about 3 of the 10 tokens are [MASK] and the model must fill them in given the other 7. At , 9 tokens are masked and the task is nearly unconditional generation. Absorbing-state discrete diffusion is therefore BERT-style masked prediction with a randomized, annealed mask ratio — that framing is the single most useful sentence to have ready, and it makes the connection to modern masked diffusion language models obvious.

Good and bad.

Advantages for NLP:

  • Non-autoregressive (can generate in parallel)
  • Better for editing tasks (text inpainting)
  • More flexible control

The costs:

  • Text is discrete, so the whole continuous-diffusion toolbox has to be rebuilt
  • Uniform-transition variants are much harder to learn than absorbing ones
  • Quality depends directly on the step count you are willing to pay for

Follow-up: How does generation actually decide which tokens to unmask? Typically confidence-based: the model predicts a distribution for every masked position, and at each reverse step you commit the positions where it is most confident and re-mask the rest. That is why these models can trade quality for latency by changing the number of steps — fewer steps means committing more tokens per step.

Why the interviewer asks this. It checks whether you can transfer a continuous-domain idea to a discrete one rather than just reciting the image-diffusion recipe.

Saying it out loud. Text is discrete, so you can’t add Gaussian noise to it. Instead the forward process randomly replaces tokens — and the version that works best replaces them with a mask token that can never change back, so after enough steps everything is masked. The reverse process predicts the original tokens. If that sounds like BERT, it basically is, except the mask ratio is randomized and annealed, and generation runs it repeatedly, unmasking the most confident positions each round.


Q99: What are use cases of diffusion models in NLP?

In 30 seconds. “For images, diffusion won outright. For text it’s a narrower story: the genuine advantage is that diffusion conditions on both sides of a gap at once, so infilling, constrained rewriting, and parallel decoding are native operations rather than special cases. It is not replacing GPT for open-ended generation.”

The short version.

Use caseStatus
Text-to-image (Stable Diffusion, DALL-E 2/3)Dominant — this is where diffusion won
Text inpainting / editing the middle of a documentGenuine structural advantage
Non-autoregressive parallel generationShipped, sold on latency; still a minority
Controllable generation (length, style, hard constraints)Real edge over autoregressive
Open-ended text generationAutoregressive still wins

The full list as usually given:

1. Non-Autoregressive Text Generation:

  • Generate all tokens in parallel
  • Faster than autoregressive models
  • Better for controlled generation

2. Text Inpainting:

  • Fill in masked tokens
  • Edit specific parts of text
  • Example: “The [MASK] sat on the [MASK]” → “The cat sat on the mat”

3. Text-to-Image:

  • DALL-E, Stable Diffusion
  • Generate images from text descriptions
  • Multimodal understanding

4. Text Editing:

  • Style transfer
  • Paraphrasing
  • Rewriting with constraints

5. Controllable Generation:

  • Generate with specific attributes
  • Control length, style, topic
  • More flexible than autoregressive

Industry Examples:

  • DALL-E: Text-to-image generation
  • Stable Diffusion: Open-source text-to-image
  • Research: Non-autoregressive text generation

Why it works.

The Structural Reason Diffusion Fits Editing:

An autoregressive model conditions only on the left. To rewrite the middle of a document it must either regenerate everything downstream or be trained with a special infilling objective. A diffusion model conditions on everything unmasked, in both directions, at every step — so “fill this hole given the surrounding text” is not a special case, it is the native operation. That is why the honest use cases for text diffusion are infilling, constrained rewriting, and parallel decoding, rather than “replacing GPT.”

Correction to the list above. DALL-E 1 (2021) was not a diffusion model — it was an autoregressive transformer over discrete VQ-VAE image tokens. Diffusion entered the DALL-E line with DALL-E 2 (unCLIP, 2022) and continued in DALL-E 3. Stable Diffusion is correctly described: it is a latent diffusion model, meaning the diffusion runs in a compressed VAE latent space (roughly for a image) rather than pixel space, which is a 48x reduction in the number of dimensions being denoised and is the reason it runs on consumer hardware.

Good and bad.

Where the Text Cases Actually Stand (as of 2026 — this is the fastest-moving item in this section):

Diffusion-style masked language models have moved from research curiosity to shipped products in the last two years, marketed on latency: because they decode many tokens per network call instead of one, they can post very high tokens-per-second on the same hardware. They remain a minority of deployed text generation, and autoregressive decoding still dominates for open-ended quality and for anything needing a long, coherent chain of reasoning. Treat any specific ranking here as having a short shelf life.

Saying it out loud. For images, diffusion won outright — Stable Diffusion and the later DALL-E models are all diffusion, though the original DALL-E actually wasn’t. For text it’s a narrower story. The genuine advantage is that diffusion conditions on both sides at once, so filling in a gap or rewriting the middle of a paragraph is the natural operation rather than a special case. And because it decodes many tokens per pass, it can be very fast. But autoregressive models still own general-purpose text generation.


Q100: How do you evaluate diffusion models?

In 30 seconds. “For images the workhorse is FID — compare Inception feature statistics between real and generated sets, lower is better, and hold the sample count fixed at fifty thousand or the numbers aren’t comparable. For text it’s messier, because a diffusion model has no exact likelihood, only a bound, so its ‘perplexity’ isn’t apples-to-apples with GPT’s. Whatever you report, report the denoising step count with it.”

The short version.

DomainMetricDirectionWatch out for
ImagesFIDLower betterBiased by sample count; convention is 50k
ImagesIS (Inception Score)Higher betterBounded by #classes, not “1-10”
ImagesReconstruction errorLower betterOnly tests denoising, not generation
TextPerplexityLower betterDiffusion gives only an ELBO upper bound
TextBLEUHigher better (0-1)Needs references
TextDistinct-n / Self-BLEUHigher distinct = more diverseDiversity only, not quality
EitherDenoising accuracy per timestepHigher betterDiffusion-specific diagnostic
EitherHuman eval / visual inspectionExpensive but decisive

The same list, spelled out:

For Images:

1. FID (Frechet Inception Distance):

  • Measures quality and diversity
  • Lower is better
  • Compares feature distributions

2. IS (Inception Score):

  • Measures quality and diversity
  • Higher is better (typically 1-10)

3. Reconstruction Error:

  • Test if model can recover original
  • Lower is better

For Text:

1. BLEU Score:

  • Measures n-gram overlap with reference
  • Higher is better (0-1)

2. Perplexity:

  • Measures how well model predicts tokens
  • Lower is better

3. Diversity Metrics:

  • Distinct-n: Ratio of unique n-grams
  • Self-BLEU: Average BLEU between samples
  • Higher distinct = more diverse

Diffusion-Specific:

1. Denoising Accuracy:

  • Test accuracy at each timestep
  • Measures how well model denoises

2. Sample Quality:

  • Visual inspection (for images)
  • Human evaluation (for text)

Why it works.

FID works because Inception features encode perceptual content, so two sets of images that look alike have similar feature statistics even when no two individual images match. IS works differently: it rewards samples the classifier labels confidently (quality) while the marginal over samples stays spread out (diversity).

Correction on Inception Score range. IS is not “typically 1-10.” It is bounded below by 1 and above by the number of classes in the classifier — 1000 for ImageNet-trained Inception. Real ImageNet generative models score in the tens to low hundreds; real ImageNet data itself scores around 233. A “1-10” band would describe only a badly broken model.

The math, and what it buys you.

How FID Is Actually Computed:

Push real images and generated images through an Inception-v3 network and take the 2048-dimensional pool3 activations. Fit a Gaussian to each set — mean and covariance — and compute the Frechet distance between them:

The first term catches a mean shift (generated images systematically differ in content), the second catches a covariance mismatch (wrong diversity or wrong correlations). Two things follow that interviewers probe. FID is biased by sample count — it decreases as grows, so numbers computed with 10k samples are not comparable to numbers computed with 50k, and 50k is the convention. And because it compares a single Gaussian fit, it is insensitive to some failure modes and sensitive to image preprocessing (resizing, JPEG) in ways that make cross-paper comparison unreliable unless the exact pipeline matches.

Good and bad.

Why Perplexity Is Awkward for Diffusion Text:

A diffusion language model does not factorize the sequence probability left to right, so it has no exact per-token likelihood. What you can compute is a variational bound on the negative log-likelihood — the ELBO — and exponentiate that, which gives an upper bound on perplexity, not perplexity itself. Comparing that number against an autoregressive model’s exact perplexity is comparing a bound to a value, and it stacks the deck against the diffusion model. Say this if asked; it is a common gotcha.

Follow-up: What single number should you report for a text diffusion model? There isn’t one. Report a generative quality measure (human preference or a task metric), a diversity measure such as distinct- or self-BLEU, and the number of denoising steps used — quality and steps trade off directly, so a quality number without a step count is meaningless.

Saying it out loud. For images the workhorse is FID, which compares Inception feature statistics between real and generated sets — lower is better, and you have to hold the sample count fixed at fifty thousand or the numbers aren’t comparable. For text it’s messier, because a diffusion model doesn’t give you an exact likelihood, only a bound, so quoting its perplexity next to GPT’s isn’t apples to apples. And whatever you report, report the number of denoising steps with it, because that’s the quality-versus-speed dial.


Q101: Compare diffusion models with autoregressive models (GPT) for text generation.

In 30 seconds. “Autoregressive decoding costs one forward pass per token; diffusion costs one pass per denoising step over the whole sequence. So diffusion is faster exactly when the step count is below the output length — five hundred tokens in thirty steps is a real win, a twenty-token answer is a loss. The price is that tokens within a step are predicted independently, so you get locally-fine, globally-inconsistent phrases.”

The short version.

Autoregressive (GPT)Diffusion
Generation orderLeft-to-right, t₁ → t₂ → t₃ → …All positions in parallel, iteratively refined
Cost for tokens passes (cheap each, KV cache) passes over full length
ConditioningAll previous tokensEverything currently unmasked, both directions
Failure modeSlow for long outputsConditional-independence errors within a step
Best atLong sequences, open-ended quality, speed on short outputsInfilling, hard constraints, parallel decoding
MaturityEstablished, dominantPromising, narrower

Generation Process:

Autoregressive (GPT):

  • Generate left-to-right, one token at a time
  • Sequential: t₁ → t₂ → t₃ → …

Diffusion:

  • Generate all tokens in parallel (discrete diffusion)
  • Iteratively refine all tokens together

Advantages:

Autoregressive:

  • Faster single-pass generation
  • Simpler implementation
  • Better for long sequences
  • More established for text

Diffusion:

  • Non-autoregressive (parallel)
  • Better for editing tasks
  • More flexible control
  • Can edit specific parts

When to Use:

Autoregressive:

  • Standard text generation
  • Long sequences
  • When speed is important

Diffusion:

  • Text editing/inpainting
  • Controlled generation
  • When need parallel generation

Current State:

  • Autoregressive (GPT) dominates text generation
  • Diffusion better for images
  • Discrete diffusion promising for text

Why it works.

One Real Asymmetry in Controllability:

Constraints like “this sequence must contain these five words” or “leave characters 40 through 80 untouched” are trivial for diffusion — clamp those positions and never unmask them. For an autoregressive model the same constraint requires beam search with lookahead heuristics or a specially trained infilling objective. If the interviewer pushes on “why would anyone use text diffusion,” this is the strongest honest answer.

The math, and what it buys you.

Putting Real Numbers on “Faster”:

The comparison is not steps versus steps, it is network evaluations versus tokens. Generating tokens autoregressively costs forward passes, each over a growing sequence but cheap per pass thanks to the KV cache. A diffusion model generating the same tokens costs forward passes over the full length, where is the number of denoising steps. So diffusion wins exactly when , and by roughly the factor . For tokens and steps that is a 16x advantage in passes — real, and it is why the parallel-decoding pitch is credible. For a short 20-token answer with , diffusion is slower. The crossover is the whole story.

Good and bad.

The Cost Diffusion Pays:

Within one denoising step, all positions are predicted independently given the current state. If “New” and “York” are both masked and each is individually likely, the model can commit “New” and “Delhi” in the same step, because nothing coordinates them. Autoregressive decoding never has this problem — every token is conditioned on all previous tokens. This is the same conditional-independence failure that plagued non-autoregressive machine translation, and it is why practical diffusion decoders commit only a few high-confidence tokens per step, which pushes back up toward and eats the speed advantage.

Follow-up: Can you combine them? Yes, and this is where the field is going: block-wise or semi-autoregressive schemes decode a chunk of tokens by diffusion, condition on it, and move to the next chunk. You get intra-block parallelism with inter-block causal conditioning, and you can keep a KV cache across blocks.

Why the interviewer asks this. They want a quantified trade-off — steps versus tokens — not a preference between two architectures.

Saying it out loud. The headline is that autoregressive models decode one token per forward pass and diffusion decodes the whole sequence per pass but needs many passes. So diffusion is faster only when your step count is smaller than your output length — for five hundred tokens in thirty steps that’s a real win, for a twenty-token answer it’s a loss. The cost is that tokens within a step are predicted independently, so the model can produce locally-fine, globally-inconsistent phrases. Autoregressive still owns general text; diffusion’s genuine edge is infilling and hard constraints.


See 40_diffusion_models/diffusion_theory.md for complete theory! See 40_diffusion_models/diffusion_code.py for continuous diffusion! See 40_diffusion_models/nlp_diffusion.py for discrete diffusion! See 40_diffusion_models/training_diffusion.py for training procedures! See 40_diffusion_models/evaluation_diffusion.py for evaluation methods! See 40_diffusion_models/diffusion_qa.md for comprehensive Q&A!


Q102: What is perplexity? How is it computed?

In 30 seconds. “Perplexity is just the exponential of your average cross-entropy loss — the same number your training loop already prints, on a different scale. Read it as ‘how many equally-likely options was the model effectively choosing between.’ Lower is better, and one is the floor.”

The short version.

  • Definition: exponentiated average negative log-likelihood over the tokens you scored.
  • Interpretation: perplexity = as uncertain as a uniform choice among options.
  • — a loss of is a perplexity of .
  • It is a geometric mean of token probabilities, so one confidently-wrong token hurts disproportionately.
  • Lower perplexity = model is more confident = better predictions.

Perplexity:

  • Metric that measures how well a probability model predicts a sample
  • Defined as exponentiated average negative log-likelihood
  • Lower perplexity = better model

Intuitive Understanding:

  • Perplexity = k means model is as uncertain as uniform choice among k options
  • If PP = 10, model thinks there are 10 equally likely next tokens
  • Lower perplexity = model is more confident = better predictions

Why it works.

Perplexity is the inverse geometric mean of the per-token probabilities, . If you had to bet on the next token and the model’s distribution were flat over candidates, you’d be right about one time in — perplexity reports that effective . This is why it is the natural unit for “how surprised was the model.”

The math, and what it buys you.

Mathematical Definition:

PP(W) = exp(-(1/N) * Σ log P(w_i | context))

Where:

  • W = (w₁, w₂, …, wₙ) is a sequence of tokens
  • P(w_i | context) is probability assigned by model
  • N is number of tokens

Computation:

1. Get Model Predictions:

logits = model(input_ids)  # (batch, seq_len, vocab_size)
probs = softmax(logits, dim=-1)

2. Get True Token Probabilities:

true_token_probs = probs[range(batch), range(seq_len), true_tokens]

3. Compute Perplexity:

nll = -log(true_token_probs).mean()  # Negative log-likelihood
perplexity = exp(nll)

Connection to Cross-Entropy:

  • Cross-entropy loss = average negative log-likelihood
  • Perplexity = exp(cross-entropy_loss)
  • Minimizing loss = minimizing perplexity

A Worked Example You Can Do in Your Head:

Suppose a model assigns the true next token probabilities , , , over a four-token sequence. The negative log-likelihoods in base 2 are bits, averaging bits, so perplexity is . Read that as: on this sequence the model was, on average, about as uncertain as picking uniformly among 4.76 options. Notice the mean is over log probabilities, not probabilities — perplexity is the inverse geometric mean of the per-token probabilities, , which is why a single token with probability near zero can dominate the whole number.

The Geometric-Mean Consequence:

Because it is a geometric mean, one catastrophic token is not averaged away. If a 100-token sequence has 99 tokens at and one at , the average NLL in bits is , giving perplexity instead of — a 14% degradation from a single token. That sensitivity is exactly why perplexity is a good training signal (it punishes confident mistakes hard) and a fragile evaluation metric.

The Line in the Code That Matters:

The snippet probs[range(batch), range(seq_len), true_tokens] is schematic rather than runnable — with two parallel range objects NumPy-style advanced indexing would require them to broadcast, and in practice you use gather along the vocabulary axis, as the fuller code in Q105 does. The idea is right: for each position, pick out the probability the model assigned to the token that actually occurred.

Good and bad.

Good: free (you already compute the loss), no references or annotators needed, punishes confident mistakes hard, which is exactly what you want from a training signal.

Bad: the same geometric-mean sensitivity makes it a fragile evaluation metric — a single pathological token can move the number by double digits.

Follow-up: Is perplexity computed on the loss you already have? Yes — if you train with mean cross-entropy in nats, perplexity is exp(loss) and nothing else needs computing. A training loss of is a perplexity of .

Saying it out loud. Perplexity is just the exponential of your average cross-entropy loss, so it’s the same number your training loop already prints, on a different scale. The interpretation is “how many equally-likely options was the model effectively choosing between.” Because it’s built from a mean of log probabilities, it’s really a geometric mean of the token probabilities — which means one token the model was confidently wrong about can wreck the score for a whole sequence.


Q103: What does perplexity mean? How do you interpret it?

In 30 seconds. “Perplexity of ten means the model was about as uncertain as choosing uniformly among ten options; lower is better and one is the floor. The thing worth flagging is that a perplexity number without its tokenizer and test set attached is not information — a bigger vocabulary means fewer, more informative tokens and a higher-looking per-token number.”

The short version.

PerplexityWhat it means
1Perfectly certain — assigns probability 1 to every observed token. Unrealistic.
10As uncertain as a uniform choice among 10 tokens. Reasonable for a good LM.
100Very uncertain — poor model or a genuinely hard task.
few hundredRoughly where a frequency-only unigram model sits on English.
Uniform random guessing. Worst case, and rarely observed.

Interpretation:

Perplexity = k means:

  • Model is as uncertain as if it had to choose uniformly among k options
  • On average, model thinks there are k equally likely next tokens

Examples:

Perplexity = 1:

  • Model is perfectly certain
  • Always predicts one token with probability 1
  • Unrealistic for real language

Perplexity = 10:

  • Model is as uncertain as uniform choice among 10 tokens
  • Reasonable for a good language model
  • Better than random (which would be vocabulary size)

Perplexity = 100:

  • Model is very uncertain
  • As confused as uniform choice among 100 tokens
  • Indicates poor model or difficult task

Perplexity = Vocabulary Size:

  • Model is as bad as random guessing
  • Worst case scenario

Typical Values:

For Language Models:

  • GPT-2 (small): ~30-50 on WikiText-103
  • GPT-2 (large): ~15-25 on WikiText-103
  • GPT-3: ~10-20 on various datasets
  • State-of-the-art: < 10 on some datasets

For Different Tasks:

  • Simple tasks: Lower perplexity (5-20)
  • Complex tasks: Higher perplexity (20-100)
  • Domain-specific: Varies widely

Connection to Entropy:

  • Perplexity = 2^H (where H is cross-entropy in bits)
  • Entropy measures uncertainty in bits
  • Perplexity measures uncertainty in “effective vocabulary size”

Why it works.

The Comparability Trap:

The quoted numbers — GPT-2 small around 37 on WikiText-103, the larger variants in the high teens to low twenties — are only meaningful because everyone evaluates them the same way. Perplexity is per token, and different models use different tokenizers. A model with a large vocabulary spends fewer tokens on the same text, so each token carries more information and its per-token perplexity is higher, even if it models the text better. Comparing a byte-level model’s perplexity to a 50k-BPE model’s perplexity is meaningless.

The Ceiling Is Not the Vocabulary Size:

The claim that a random model scores perplexity equal to vocabulary size is right only for a uniform random model. A unigram model that just knows token frequencies already scores far below — on English text, something in the hundreds against a 50k vocabulary. So “as bad as random” in practice means a few hundred, not 50,000, and a model at perplexity 500 is not at chance, it is worse than a bigram counter.

The math, and what it buys you.

The fix for the comparability trap is to renormalize to a fixed unit. If your model needs tokens to cover words, word-level perplexity is

Bits per byte does the same thing with bytes as the denominator, and is the standard for cross-tokenizer comparison.

Worked Number:

Suppose a BPE model reports token perplexity over 1.3 tokens per word. Then nats per token, nats per word, so word perplexity is — more than double the token number. Two models can be reported at “perplexity 12” and differ by a wide margin once you put them on the same unit.

Follow-up: Can perplexity go below 1? No. Perplexity is of a non-negative quantity (average NLL), so its floor is exactly 1, reached only when the model assigns probability 1 to every observed token.

Why the interviewer asks this. Anyone can recite the formula; the interpretation question checks whether you know that a perplexity number without its tokenizer and dataset attached is not information.

Saying it out loud. Perplexity of ten means the model was about as uncertain as choosing uniformly among ten options, and lower is better with one as the floor. The thing I’d flag is that you can’t compare perplexities across models with different tokenizers or different test sets — a bigger vocabulary means fewer, more informative tokens and a higher-looking per-token number. If I actually need to compare two models, I convert to bits per byte or per-word perplexity first.


In 30 seconds. “They’re the same thing in different units. Entropy counts your uncertainty in bits; perplexity exponentiates that into an effective number of choices — 3.32 bits and 10 options are the same statement. You compute cross-entropy rather than entropy because you never have the true distribution, and cross-entropy always exceeds it by exactly the KL divergence between the language and your model.”

The short version.

QuantityFormulaUnit
EntropyH(X) = -Σ P(x) * log P(x)bits (log₂) or nats (ln)
Cross-entropyH(P, Q) = -Σ P(x) * log Q(x)same
LM lossH = -(1/N) * Σ log P(w_i | context)nats, as frameworks report it
PerplexityPP = 2^H (bits) or exp(H) (nats)“effective vocabulary size”
Bits per tokenBPT = log₂(PP) = H in bitsbits

Entropy:

H(X) = -Σ P(x) * log P(x)

Perplexity:

PP = 2^H(X)  (for base-2 log)
PP = exp(H(X))  (for natural log)

Cross-Entropy:

H(P, Q) = -Σ P(x) * log Q(x)

For Language Models:

H = -(1/N) * Σ log P(w_i | context)

Perplexity:

PP = exp(H)

Key Insight:

  • Cross-entropy loss = average negative log-likelihood
  • Perplexity = exp(cross-entropy_loss)
  • Minimizing cross-entropy = minimizing perplexity
  • They are equivalent objectives

Training:

  • When training language models, we minimize cross-entropy
  • This is equivalent to minimizing perplexity
  • Lower loss = lower perplexity = better model

Bits per Token:

  • BPT = log₂(PP) = H (in bits)
  • Lower BPT = lower perplexity = better model
  • More interpretable for some applications

Why it works.

Where the Exponential Comes From:

The connection is not a coincidence, it is a definition unwound. Entropy in bits is the average number of yes/no questions needed to identify the outcome. If you need bits, you are distinguishing among equally likely possibilities. Perplexity is defined as that count: in bits, in nats. So perplexity and entropy are the same quantity in different units — “3.32 bits of uncertainty” and “effectively 10 choices” are the same sentence.

Why It Is Cross-Entropy and Not Entropy:

The true distribution over language is unknown, so you cannot compute . What you can compute is , estimated by averaging over held-out samples that are drawn from . And because , model perplexity is always an upper bound on the true entropy of the language, with the gap being exactly the KL divergence between the language and your model. Driving perplexity down is literally driving down, since is a constant you cannot touch. That sentence is the best single answer to “why do we minimize cross-entropy.”

The math, and what it buys you.

Intuition:

  • Entropy: uncertainty in bits
  • Perplexity: uncertainty in “effective vocabulary size”
  • If entropy = log₂(10) ≈ 3.32 bits, perplexity = 2^3.32 ≈ 10

Unit Conversion, Concretely:

Frameworks report loss in nats (natural log). ; bits per token is . A loss of nats is bits per token and a perplexity of . Going the other way, Shannon’s classic estimate of English at roughly bit per character corresponds to a per-character perplexity of .

Follow-up: If two models have the same perplexity, are they the same model? No. Perplexity is one scalar summarizing an average over a distribution; two models can agree on the mean log-likelihood while disagreeing badly on individual examples, on calibration, and on generation quality.

Saying it out loud. They’re the same thing in different units. Entropy counts your uncertainty in bits; perplexity exponentiates that to give an effective number of choices, so three point three bits and ten options are the same statement. In practice you never have the true distribution, so you compute cross-entropy against held-out data, and that’s always at least the true entropy — the excess is exactly the KL divergence between the language and your model. So minimizing loss is literally minimizing that divergence.


Q105: How do you compute perplexity for a language model? Show the code.

In 30 seconds. “Log-softmax the logits, gather the log-probability of the token that actually occurred at each position, average, negate, exponentiate. Three things trip everyone up: shift by one so position i predicts token i+1, mask out padding, and accumulate total loss and total tokens across the dataset and exponentiate once at the end — never average per-batch perplexities.”

The short version.

  • log_softmax over the vocab axis.
  • gather the target token’s log-prob at each position.
  • Shift: logits[:, :-1, :] against labels[:, 1:].
  • Mask padding out of both the numerator and the token count.
  • Sum NLL and tokens over the whole dataset, then exp once.

The math, and what it buys you.

Step-by-Step Algorithm:

1. Get Model Predictions:

logits = model(input_ids)  # (batch, seq_len, vocab_size)

2. Get Log Probabilities:

log_probs = F.log_softmax(logits, dim=-1)

3. Get True Token Log Probabilities:

batch_size, seq_len = targets.shape
indices = targets.unsqueeze(-1)  # (batch, seq, 1)
true_token_log_probs = log_probs.gather(dim=-1, index=indices).squeeze(-1)

4. Compute Average Negative Log-Likelihood:

if mask is not None:
    nll = -(true_token_log_probs * mask).sum() / mask.sum()
else:
    nll = -true_token_log_probs.mean()

5. Compute Perplexity:

perplexity = torch.exp(nll).item()

Complete Function:

def perplexity_from_logits(logits, targets, mask=None):
    # Get log probabilities
    log_probs = F.log_softmax(logits, dim=-1)
    
    # Get true token log probabilities
    indices = targets.unsqueeze(-1)
    true_token_log_probs = log_probs.gather(dim=-1, index=indices).squeeze(-1)
    
    # Average negative log-likelihood
    if mask is not None:
        nll = -(true_token_log_probs * mask).sum() / mask.sum()
    else:
        nll = -true_token_log_probs.mean()
    
    # Perplexity
    return torch.exp(nll).item()

For Language Model Evaluation:

def language_model_perplexity(model, dataloader, device='cpu'):
    model.eval()
    total_nll = 0.0
    total_tokens = 0
    
    with torch.no_grad():
        for batch in dataloader:
            input_ids = batch['input_ids'].to(device)
            labels = batch['labels'].to(device)
            
            # Forward pass
            logits = model(input_ids).logits
            
            # Shift for next token prediction
            shift_logits = logits[:, :-1, :]
            shift_labels = labels[:, 1:]
            
            # Compute perplexity
            pp = perplexity_from_logits(shift_logits, shift_labels)
            
            # Accumulate
            batch_tokens = shift_labels.numel()
            total_nll += np.log(pp) * batch_tokens
            total_tokens += batch_tokens
    
    # Average perplexity
    avg_pp = np.exp(total_nll / total_tokens)
    return avg_pp

See 03_evaluation_metrics/perplexity_code.py for complete implementation!

Runnable Check (executed):

import torch, torch.nn.functional as F, math

torch.manual_seed(0)
V = 7
logits = torch.zeros(1, 4, V)
# force known probabilities 0.5, 0.25, 0.125, 0.125 on the target tokens
targets = torch.tensor([[0, 1, 2, 3]])
for i, p in enumerate([0.5, 0.25, 0.125, 0.125]):
    rest = (1 - p) / (V - 1)
    row = torch.full((V,), math.log(rest))
    row[targets[0, i]] = math.log(p)
    logits[0, i] = row

log_probs = F.log_softmax(logits, dim=-1)
nll = -log_probs.gather(-1, targets.unsqueeze(-1)).squeeze(-1).mean()
print(float(nll))                 # 1.5596 nats
print(float(torch.exp(nll)))      # 4.7568  -> matches 2**2.25 from Q97
print(2 ** 2.25)                  # 4.7568

Why it works.

Why gather and Not Fancy Indexing:

log_probs has shape (batch, seq_len, vocab) and targets has shape (batch, seq_len). targets.unsqueeze(-1) makes it (batch, seq_len, 1), and gather(dim=-1, index=...) picks, at every (batch, position) slot, the single vocabulary entry named by the target. The result is (batch, seq_len, 1), and squeeze(-1) drops back to (batch, seq_len). This is the vectorized version of “for each position, look up the log-probability of the token that actually occurred,” and it does it without materializing anything vocabulary-sized beyond the log-softmax itself.

Why the Shift by One:

shift_logits = logits[:, :-1, :] and shift_labels = labels[:, 1:] implement next-token prediction. Position of the logits predicts token , so the last logit has no target and the first label has no predictor. Forgetting this shift is the single most common perplexity bug and it produces a suspiciously low number, because the model appears to be predicting the token it was just shown.

Good and bad.

A Real Bug in the Aggregation Function:

The language_model_perplexity loop calls perplexity_from_logits without passing a mask, so padding tokens are counted as real tokens. If your batches are padded, every pad position contributes its (usually very confident, because pad is easy) log-probability to the average and the reported perplexity is biased low. The batch_tokens = shift_labels.numel() line has the same problem: it counts padded slots. The fix is to build a mask from the label tensor (shift_labels != pad_id), pass it in, and accumulate mask.sum() rather than numel(). Round-tripping through np.log(pp) to recover the NLL is mathematically harmless but pointlessly lossy — returning the summed NLL and the token count directly is cleaner.

The Long-Document Subtlety:

If a document is longer than the context window you must chunk it, and naive non-overlapping chunks penalize the model unfairly: the first token of every chunk is predicted with no context at all. The standard fix is a strided sliding window — advance by, say, 512 tokens through a 1024-token window and score only the newly revealed 512 — which roughly halves compute cost relative to a stride of 1 while giving every scored token real context. Reported perplexities move by several points depending on the stride, so it must be stated alongside the number.

Follow-up: Why not just average the per-batch perplexities? Because perplexity is exponential in the mean loss, and the mean of exponentials is not the exponential of the mean (Jensen’s inequality) — averaging perplexities overestimates. Accumulate total NLL and total token count, then exponentiate once at the end.

Why the interviewer asks this. Perplexity code is short enough to write on a whiteboard and has three classic bugs, so it is an efficient correctness probe.

Saying it out loud. The computation is: log-softmax the logits, gather the log-probability of the token that actually occurred at each position, average those, negate, and exponentiate. Three things trip people up. You have to shift by one so position i predicts token i plus one. You have to mask out padding, or pad tokens flatter your number. And you have to accumulate total loss and total tokens across the whole dataset and exponentiate once at the very end, not average per-batch perplexities — the exponential doesn’t commute with averaging.


Q106: What are the limitations of perplexity? When should you use other metrics?

In 30 seconds. “Perplexity measures how well a model assigns probability to text that already exists, which is not the same as how good the text it generates is — human text isn’t maximally likely, so maximizing likelihood at decode time gives you robotic output. It also isn’t comparable across tokenizers or test sets, and an RLHF’d chat model usually has worse perplexity than its base model while being far more useful.”

The short version.

Use perplexity forReach for something else when
Pretraining loss curves, checkpoint comparisonYou care about factuality → task accuracy, grounded QA
Tokenizer and data-mixture ablationsYou care about instruction following → pairwise preference
Scaling-law fitsYou care about repetition → distinct-, repetition rate
Quantization damageYou care about confidence → expected calibration error
Same architecture, same tokenizer, same test setYou are evaluating an aligned/RLHF model at all

Limitations:

1. Not Always Correlates with Quality:

  • Lower perplexity doesn’t always mean better text
  • Can overfit to training data
  • May not reflect human judgment
  • Need other metrics (BLEU, ROUGE, human eval)

2. Dataset Dependent:

  • Perplexity varies by dataset
  • Can’t compare across different datasets
  • Need same preprocessing
  • Fair comparison requires same setup

3. Vocabulary Size Matters:

  • Larger vocabulary = higher baseline perplexity
  • Need to account for vocabulary size
  • Normalized perplexity helps
  • Compare models with similar vocabularies

4. Sequence Length:

  • Longer sequences = more stable estimate
  • Shorter sequences = more variable
  • Need sufficient data for reliable estimate

5. Task-Specific:

  • Good perplexity doesn’t guarantee good performance on downstream tasks
  • May not reflect task-specific quality
  • Need task-specific metrics

Why it works — and where it breaks.

The Sharpest Version of “Doesn’t Correlate with Quality”:

Perplexity measures how well a model assigns probability to text that already exists. Generation quality depends on what the model produces when it is sampling from itself, which is a different distribution — the model’s own outputs are not drawn from the test set. These come apart concretely: greedy decoding from a low-perplexity model produces repetitive, degenerate text, while nucleus sampling from the same model produces much better text and higher measured perplexity on its own outputs. Holtzman et al. made exactly this point — human text has moderate, variable per-token likelihood, and text optimized to be maximally likely does not look human.

Perplexity is also blind to everything the loss does not see. An RLHF-tuned chat model typically has worse perplexity on raw web text than its base model, and is dramatically more useful. If you are evaluating an aligned model, perplexity is close to the wrong instrument.

Good and bad.

When It Is Genuinely the Right Metric:

Pretraining loss curves, tokenizer and data-mixture ablations, scaling-law fits, quantization damage, and any comparison of two checkpoints of the same architecture on the same tokenizer and same test set. In all of these you are asking “did the model’s density estimate improve,” which is exactly what perplexity answers, and it has the enormous practical virtue of needing no references, no annotators, and no decoding.

What to Reach for Instead, by Failure Mode:

If you care about factuality, perplexity cannot help — use task accuracy or a grounded QA benchmark. If you care about instruction following, use pairwise human or model-judged preferences. If you care about repetition and degeneration, use distinct- and repetition rate. If you care about calibration, use expected calibration error, not perplexity, since a model can have good average likelihood and badly miscalibrated confidence.

When to Use Other Metrics:

1. Text Generation:

  • Use BLEU, ROUGE for quality
  • Use diversity metrics (distinct-n)
  • Use human evaluation
  • Perplexity as one of many metrics

2. Machine Translation:

  • Use BLEU as primary metric
  • Use METEOR, TER
  • Perplexity for model selection

3. Summarization:

  • Use ROUGE as primary metric
  • Use BLEU, METEOR
  • Perplexity for training monitoring

4. Question Answering:

  • Use EM (Exact Match), F1
  • Use BLEU for generation quality
  • Perplexity less relevant

Best Practices:

  • Use perplexity for model selection during training
  • Combine with task-specific metrics
  • Don’t rely only on perplexity
  • Consider context and task requirements

Follow-up: A team reports their new model has 20% lower perplexity. What do you ask? Same tokenizer? Same test set, and was it decontaminated against the training data? Same evaluation stride and context length? Same handling of padding and end-of-document tokens? Any one of those can produce a 20% swing with no modelling improvement at all.

Why the interviewer asks this. Perplexity is the metric people quote most and interrogate least, so it is an efficient probe for whether a candidate evaluates critically or just reports.

Saying it out loud. Perplexity tells you how well the model assigns probability to text that already exists, which isn’t the same as how good the text it generates is. The classic example is that maximizing likelihood at decode time gives you repetitive, robotic output — human text isn’t maximally likely. It’s also not comparable across tokenizers or test sets, and an RLHF’d chat model usually has worse perplexity than its base model while being far more useful. I’d use it for pretraining ablations and checkpoint comparisons, and reach for task metrics or human preference for anything user-facing.


See 03_evaluation_metrics/perplexity_detailed.md for complete theory! See 03_evaluation_metrics/perplexity_code.py for complete code! See 33_information_theory/information_theory.py for entropy implementation!


Causal Attention

Q107: Explain causal attention. What does the code np.tril(np.ones((seq_len, seq_len))) do?

In 30 seconds.np.tril takes the lower triangle of an all-ones matrix, so row i has ones in columns zero through i and zeros after. Read a row as ‘which positions may I look at’ — position two sees zero, one, and itself, nothing beyond. You add negative infinity wherever the mask is zero, then softmax, so future positions get exactly zero weight.”

The short version.

  • np.ones((seq_len, seq_len)) → all-ones matrix.
  • np.tril() → zeroes everything strictly above the diagonal.
  • Row = the allowed attention set for query position .
  • Applied as an additive before softmax, not a multiply after.
  • Beware the convention: some libraries hand you the inverted block mask.

Causal Attention:

  • Masks future positions to enforce autoregressive property
  • Each position can only attend to itself and previous positions
  • Critical for GPT-style models (autoregressive generation)

Why it works.

1. np.ones((seq_len, seq_len)):

  • Creates matrix of all 1s
  • Shape: (seq_len, seq_len)
  • Example for seq_len=4:
[[1, 1, 1, 1],
 [1, 1, 1, 1],
 [1, 1, 1, 1],
 [1, 1, 1, 1]]

2. np.tril():

  • Takes lower triangular part
  • Sets everything above diagonal to 0
  • Keeps everything on and below diagonal as is
  • Result:
[[1, 0, 0, 0],   ← Position 0: can only see itself
 [1, 1, 0, 0],   ← Position 1: can see 0, 1
 [1, 1, 1, 0],   ← Position 2: can see 0, 1, 2
 [1, 1, 1, 1]]   ← Position 3: can see all (0, 1, 2, 3)

3. Application:

  • Mask applied to attention scores
  • scores[mask == 0] = -∞ (future positions)
  • After softmax: Future positions get 0 attention weight
  • Result: Each position only attends to past and current

Why Lower Triangular?

  • Lower triangular = can attend to positions ≤ current (past + current)
  • Upper triangular = wrong (would allow future, block past)
  • This enforces causal constraint for autoregressive generation

Why It Is a Mask and Not Just “Don’t Compute Those”:

You could imagine skipping the upper-triangular entries entirely, and fused kernels like FlashAttention do exactly that. But in a plain implementation the scores are produced by one dense matmul Q @ K.T, which computes all entries whether you want them or not. The mask is applied afterwards as an additive so that the softmax — which normalizes across each row — assigns those entries zero weight. The mask is a correctness device, not an efficiency device; the efficiency version requires a kernel that never materializes the blocked tiles.

The math, and what it buys you.

The Code:

mask = np.tril(np.ones((seq_len, seq_len)))

Runnable Version (executed):

import numpy as np

seq_len = 4
keep = np.tril(np.ones((seq_len, seq_len)))
scores = np.array([[2.3, 9.9, 9.9, 9.9],
                   [1.8, 2.1, 9.9, 9.9],
                   [1.2, 1.7, 2.0, 9.9],
                   [0.9, 1.4, 1.6, 2.2]])
masked = np.where(keep == 1, scores, -np.inf)
w = np.exp(masked - masked.max(axis=1, keepdims=True))
w = w / w.sum(axis=1, keepdims=True)
print(np.round(w, 3))
# [[1.    0.    0.    0.   ]
#  [0.426 0.574 0.    0.   ]
#  [0.205 0.338 0.457 0.   ]
#  [0.12  0.198 0.242 0.44 ]]  <- rows sum to 1, upper triangle exactly 0
print(w.sum(axis=1))  # [1. 1. 1. 1.]

Note that the deliberately large 9.9 scores in the upper triangle have zero influence: masking happens before the exponential, so the forbidden entries cannot leak in no matter how large they are. That is the payoff of doing it additively — correctness is unconditional on the score magnitudes.

Good and bad.

The Convention Trap:

Note that np.tril produces a keep mask: 1 means allowed. PyTorch’s torch.triu(torch.ones(n, n), diagonal=1) produces the complementary block mask: 1 means forbidden. Both appear constantly and they are inverses of each other, so masked_fill(mask == 0, -inf) and masked_fill(mask == 1, -inf) are both correct code depending on which convention produced the mask. Getting this backwards gives you a model that can see only the future, which trains to a suspiciously low loss and generates nonsense.

One Numerical Gotcha:

Use a large negative finite number (or -torch.inf with care) rather than computing exp of a genuine -inf inside an unstable softmax. If an entire row is masked — which happens with padded sequences where a query position has no valid keys — softmax over all produces NaN, and the NaN then propagates through the whole batch. This is a real production bug, not a theoretical one.

See 05_attention_mechanisms/causal_attention_detailed.md for complete explanation!

Follow-up: Why is the mask usually a registered buffer, not a parameter? Because it is fixed, has no gradient, and depends only on the maximum sequence length. Registering it as a buffer means it moves to the GPU with .to(device) and is built once rather than reallocated every forward pass.

Saying it out loud. np.tril takes the lower triangle of an all-ones matrix, so row i has ones in columns zero through i and zeros after that. Read a row as “which positions may I look at” — position two can see zero, one, and itself, and nothing beyond. You add negative infinity wherever the mask is zero, then softmax, so the future positions get exactly zero weight and each row still sums to one. The trap is the convention: some libraries hand you the opposite mask, where one means blocked.


Q108: Why do we need causal attention? What happens without it?

In 30 seconds. “Without the mask, every position can see the token that comes after it, and the cheapest way to predict the next token is to copy it. Training loss falls through the floor, the model learns nothing, and at inference the future isn’t there so it produces garbage. The mask is what makes training match inference — and it’s also what lets one forward pass give you a training signal at every position at once.”

The short version.

With causal maskWithout
TrainingPosition sees Position sees everything, including
InferencePosition sees — identicalFuture doesn’t exist; model is out of distribution
Training lossTracks the entropy of languageCollapses toward — implausibly low
GenerationCoherentIncoherent
DiagnosisLow loss + garbage output = missing mask

Why it works.

Concretely, What “Learning to Cheat” Looks Like:

Without the mask, the fastest way to reduce loss at position is to attend to position and copy it, because position ’s embedding is the answer. It is like an exam where the answer key is printed on the back of the question sheet — a single attention head learns to flip the page in a handful of steps. Training loss collapses toward zero — you will see per-token loss in the range, far below the entropy of language, which is the tell. Then at generation time position does not exist, the head attends to padding or to the last real token, and the output is incoherent. The symptom pair — implausibly low training loss, garbage generation — is the classic signature of a missing causal mask, and interviewers ask it as a debugging question.

Why We Need It (the standard framing):

Autoregressive Constraint:

  • In autoregressive generation, tokens are generated left-to-right
  • When generating token at position i, only tokens 0…i-1 exist
  • Future tokens (i+1, i+2, …) don’t exist yet
  • Model should only use information from past and current tokens

During Training:

  • Model sees full sequence: [token_0, token_1, …, token_n]
  • Without mask: Each position can attend to ALL positions (including future)
  • Model learns to use future tokens for prediction

During Inference:

  • Generate one token at a time
  • At step i, only have [token_0, …, token_{i-1}]
  • Future tokens don’t exist
  • But model was trained to use future tokens!

Result:

  • Training and inference mismatch
  • Model behavior inconsistent
  • Poor generation quality

With Causal Mask:

  • Training: Each position only sees past/current (matches inference)
  • Inference: Each position only sees past/current (matches training)
  • Consistent behavior → good generation

Example:

  • Without mask: Position 1 can see position 2 (future) during training
  • With mask: Position 1 cannot see position 2 (future) during training
  • This matches inference where position 2 doesn’t exist yet

The math, and what it buys you.

Why Not Just Train One Position at a Time?

You could avoid the mask by feeding the model prefix and training only on , for each separately. That is correct but costs forward passes per sequence. The causal mask is what lets a single forward pass over a length- sequence produce training signals simultaneously — every position predicts its successor, all in parallel. Teacher forcing plus causal masking is the reason transformer pretraining is affordable at all; it is an -fold efficiency win, not just a correctness patch.

Good and bad.

Where You Deliberately Don’t Want It:

BERT-style encoders use bidirectional attention on purpose, because they are not generating — a classifier or a token tagger benefits from seeing both sides, and the training objective (masked LM) supplies the information-hiding instead. Encoder-decoder models are mixed: the encoder is bidirectional, the decoder self-attention is causal, and the cross-attention from decoder to encoder is unmasked. So “always use a causal mask” is wrong; the rule is “mask exactly when the inference-time information set is a prefix.”

Follow-up: During incremental generation with a KV cache, do you still need the mask? Not for the new token: at step the cache contains only positions , so there is nothing in the future to mask — the constraint is enforced by what exists in the cache. You still need it during the prefill pass over the prompt, which processes all prompt positions at once.

Why the interviewer asks this. Framed as a debugging question — implausibly low training loss with incoherent generation — it is one of the most common real failures in hand-written transformer code.

Saying it out loud. Without the mask, every position can see the token that comes after it, and the easiest way to predict the next token is to just copy it. So training loss falls through the floor and the model learns nothing useful, and then at inference the future isn’t there and it produces garbage. The mask is what makes training match inference. It’s also what lets one forward pass give you a training signal at every position at once, instead of running the model separately for each prefix.


Q109: How does the causal mask work mathematically?

In 30 seconds. “You add a matrix to the raw scores where allowed positions get zero and future positions get negative infinity, then softmax. The negative infinities exponentiate to zero, so they drop out of the denominator too, and each row normalizes over exactly the positions it’s allowed to see. Doing it multiplicatively after the softmax would be wrong.”

The short version.

Standard Attention:

Attention(Q, K, V) = softmax(QK^T / √d_k) V

Causal Attention:

Attention(Q, K, V) = softmax((QK^T / √d_k) + M) V

Where M is the causal mask:

M[i, j] = {
    0   if j ≤ i  (can attend to past/current)
    -∞  if j > i  (cannot attend to future)
}

Step-by-Step:

1. Compute Attention Scores:

scores = Q @ K.T / √d_k  # Shape: (seq_len, seq_len)

2. Apply Causal Mask:

masked_scores = scores + M
# Where M[i, j] = -∞ if j > i (future positions)

3. Softmax:

attention_weights = softmax(masked_scores)

What Happens:

  • Future positions: scores = -∞ → softmax(-∞) = 0
  • Past/current positions: scores = original → softmax(original) = normal weights

Result:

  • Each position gets attention weights that sum to 1
  • Future positions always have 0 weight
  • Past/current positions have non-zero weights

Why it works.

Why Additive Rather Than Multiplicative Zeroing:

You could multiply the post-softmax weights by the 0/1 mask instead, but then the rows no longer sum to 1 and you would have to renormalize — and worse, the softmax denominator would still have included the future terms, so the surviving weights would be wrong before renormalization. Adding before the softmax removes those terms from the denominator itself, so the normalization is exactly over the allowed set. Formally, for row :

The math, and what it buys you.

Example for seq_len=4:

Mask Matrix:

M = [[0,  -∞, -∞, -∞],
     [0,  0,  -∞, -∞],
     [0,  0,  0,  -∞],
     [0,  0,  0,  0]]

After Adding Mask:

scores = [[2.3, -∞,  -∞,  -∞],
          [1.8, 2.1, -∞,  -∞],
          [1.2, 1.7, 2.0, -∞],
          [0.9, 1.4, 1.6, 2.2]]

After Softmax:

weights = [[1.0, 0.0, 0.0, 0.0],   ← Position 0: 100% to itself
           [0.4, 0.6, 0.0, 0.0],   ← Position 1: 40% to 0, 60% to 1
           [0.2, 0.3, 0.5, 0.0],   ← Position 2: distributed, 0% to future
           [0.1, 0.2, 0.3, 0.4]]   ← Position 3: distributed across all

Verifying the Worked Example (executed):

The illustrative weights in the answer above are rounded for readability; the exact softmax of the given scores is:

import numpy as np
s = np.array([[2.3, -np.inf, -np.inf, -np.inf],
              [1.8, 2.1, -np.inf, -np.inf],
              [1.2, 1.7, 2.0, -np.inf],
              [0.9, 1.4, 1.6, 2.2]])
e = np.exp(s - s.max(1, keepdims=True))
print(np.round(e / e.sum(1, keepdims=True), 3))
# [[1.    0.    0.    0.   ]
#  [0.426 0.574 0.    0.   ]
#  [0.205 0.338 0.457 0.   ]
#  [0.12  0.198 0.242 0.44 ]]

So row 1 is rather than the rounded , and row 3 is . Every row still sums to exactly 1 and the upper triangle is exactly 0, which is the property that matters.

The Structural Consequence:

Row of the attention matrix has non-zero entries, so the number of live entries is — just over half of . For that is 8.4M live entries versus 16.8M computed, so a naive masked implementation throws away almost exactly half its attention FLOPs. FlashAttention with a causal flag skips entire blocks above the diagonal and recovers close to that full 2x. This is a good number to have ready when someone asks what causal masking costs.

Follow-up: Does the mask change the gradient? Yes, and cleanly: since the masked entries are removed from the softmax denominator, their gradient is exactly zero, so no gradient ever flows to a future position’s key or value through a masked edge. Nothing special is needed in the backward pass.

Saying it out loud. You add a matrix to the raw scores where allowed positions get zero and future positions get negative infinity, then softmax. Because the negative infinities exponentiate to zero, they drop out of the denominator too, so each row normalizes over exactly the positions it’s allowed to see. Doing it multiplicatively after the softmax would be wrong, because the future terms would already be in the denominator. And note the causal structure means only about half the score matrix is ever live, which is what the causal-aware fast kernels exploit.


See 05_attention_mechanisms/causal_attention_detailed.md for complete theory! See 05_attention_mechanisms/causal_attention_code.py for visualization!


Advanced Attention Mechanisms (GQA, Paged Attention)

Q110: What is Group Query Attention (GQA)? How does it differ from Multi-Head Attention?

In 30 seconds. “In normal multi-head attention every head has its own keys and values, so the KV cache scales with head count. GQA keeps all the query heads separate but has groups of them share one set of keys and values — Llama-2-70B has sixty-four query heads and eight KV groups, so the cache is eight times smaller. Quality barely moves, because the head-specific information mostly lives in the queries.”

The short version.

MHAGQA
Q projectionsone per headone per head (unchanged)
K, V projectionsone per headone per group
KV cachenum_heads × seq_len × (d_k + d_v)num_groups × seq_len × (d_k + d_v)
Parameters3 × num_heads × d_model²num_heads × d_model² + 2 × num_groups × d_model²
Qualitybestvery close to best
Usetraining, researchproduction inference — the default

Example: 32 heads, 8 groups

  • MHA: 32 × seq_len × (d_k + d_v) KV cache
  • GQA: 8 × seq_len × (d_k + d_v) KV cache
  • Reduction: 4× in KV cache memory

Group Query Attention (GQA):

  • Groups heads and shares K, V within each group
  • Middle ground between MHA and MQA
  • Reduces KV cache memory while maintaining quality

Key Differences:

Multi-Head Attention (MHA):

  • Each head has separate Q, K, V
  • KV Cache: num_heads × seq_len × (d_k + d_v)
  • Parameters: 3 × num_heads × d_model²

Group Query Attention (GQA):

  • Heads grouped, K, V shared within each group
  • Q separate per head (like MHA)
  • KV Cache: num_groups × seq_len × (d_k + d_v)
  • Parameters: num_heads × d_model² + 2 × num_groups × d_model²

Why It Works:

  • Queries need to be different (capture different aspects)
  • Keys and values can be shared within groups
  • Maintains most of MHA’s expressiveness
  • Significant memory reduction

Why it works.

Queries encode “what am I looking for”; keys and values encode “what’s in the library.” You can give sixty-four researchers their own question list while they all consult one shared card catalogue — the questions are what differ, not the catalogue.

Why It Doesn’t Hurt Quality Much:

The GQA paper’s argument is empirical: they take a trained MHA checkpoint, mean-pool the key and value projections within each group to initialize the smaller matrices, and “uptrain” for about 5% of the original pretraining compute. The recovered model sits very close to MHA quality and far above MQA. The intuition is that queries carry the head-specific “what am I looking for,” while keys and values are closer to a shared content representation, so heads can share a lookup table without collapsing into each other.

The math, and what it buys you.

Exact Shapes, One Layer:

Let , query heads, (so ), and groups — these are Llama-2-70B’s numbers. Then per layer:

  • , producing of shape (batch, 64, seq, 128)
  • , producing of shape (batch, 8, seq, 128)

Before the score matmul you repeat_interleave the KV heads by , expanding (batch, 8, seq, 128) to (batch, 64, seq, 128). Every group of 8 consecutive query heads then shares one physical key/value tensor. Crucially the expansion is a view-level broadcast at compute time — you never store the 64-head version in the cache, and that is where the saving comes from.

The Memory Number That Actually Matters:

KV cache bytes per token . For Llama-2-70B at fp16 with 80 layers:

SchemePer token4096-token sequence32 concurrent sequences
MHA642.50 MiB10.0 GiB320 GiB
GQA8320 KiB1.25 GiB40 GiB
MQA140 KiB0.16 GiB5 GiB

The MHA row is the point: 32 concurrent 4k-token sequences would need 320 GiB of KV cache alone — more than four H100s, before the 140 GiB of weights. GQA brings that to 40 GiB and makes the deployment possible. This table is the single most useful thing to be able to reconstruct in an interview, because it turns “GQA saves memory” into “GQA is the difference between serving 4 requests and serving 32.”

Good and bad.

Good: 4-8x smaller cache, negligible quality cost, free at training time, and it is a one-parameter dial you can tune to your memory budget.

Bad: still more cache than MQA; requires a repeat_interleave in the attention path; and if you inherit an MHA checkpoint you must uptrain to convert it.

When to Use:

  • Production inference (recommended)
  • Need efficiency but maintain quality
  • Best balance between MHA and MQA

Follow-up: What is GQA with , and with ? Exactly MHA and exactly MQA respectively — GQA is the one-parameter family that interpolates between them, which is why it superseded both as the default.

Why the interviewer asks this. GQA is in essentially every open-weights model shipped since 2023, so not knowing it signals you have not looked inside a modern config file.

Saying it out loud. In normal multi-head attention every head has its own keys and values, so the KV cache scales with the number of heads. Grouped-query attention keeps all the query heads separate but has groups of them share one set of keys and values — Llama-2 70B has sixty-four query heads and eight KV groups, so the cache is eight times smaller. In real terms that’s the difference between ten gigabytes and one and a quarter per four-thousand-token sequence. Quality barely moves, because the head-specific information mostly lives in the queries.


Q111: What is Multi-Query Attention (MQA)? How does it reduce memory?

In 30 seconds. “MQA takes sharing to the limit — all heads share a single set of keys and values, so the cache shrinks by the head count, thirty-two times for a thirty-two-head model. The reason that matters isn’t capacity, it’s bandwidth: generating each token means reading the whole cache from memory, and decoding is bandwidth-bound. The cost is that heads can no longer specialize what they retrieve against.”

The short version.

MHAMQA
Per headQ_1, K_1, V_1Q_h, K_h, V_hQ_1, K_shared, V_sharedQ_h, K_shared, V_shared
KV cachenum_heads × seq_len × (d_k + d_v)1 × seq_len × (d_k + d_v)
Cache reductionnum_heads× (e.g. 32× for 32 heads)
Parameters3 × num_heads × d_model²num_heads × d_model² + 2 × d_model²
Qualitybestgood, slight loss — worst on long-context recall

Multi-Query Attention (MQA):

  • Shares K and V across ALL heads
  • Only Q is separate per head
  • Maximum memory reduction

Key Difference:

MHA:

Head 1: Q_1, K_1, V_1
Head 2: Q_2, K_2, V_2
...
Head h: Q_h, K_h, V_h

MQA:

Head 1: Q_1, K_shared, V_shared
Head 2: Q_2, K_shared, V_shared
...
Head h: Q_h, K_shared, V_shared

Example: 32 heads, seq_len=2048, d_k=128

  • MHA KV Cache: 32 × 2048 × 256 = 16.8M values
  • MQA KV Cache: 1 × 2048 × 256 = 0.5M values
  • Reduction: 32× (16.8M → 0.5M)

Memory Reduction:

KV Cache:

  • MHA: num_heads × seq_len × (d_k + d_v)
  • MQA: 1 × seq_len × (d_k + d_v) (shared, not per head!)
  • Reduction: num_heads× (e.g., 32× for 32 heads)

Parameters:

  • MHA: 3 × num_heads × d_model²
  • MQA: num_heads × d_model² + 2 × d_model²
  • Reduction: From 3×num_heads to (num_heads + 2)

Why It Works: Why it works.

  • Queries represent “what am I looking for?” (different per head)
  • Keys represent “what information do I have?” (can be shared)
  • Values represent “what is the information?” (can be shared)
  • Same information, different queries → similar quality

Why Memory Bandwidth, Not Capacity, Is the Real Win:

Autoregressive decoding is memory-bandwidth-bound, not compute-bound. At each generated token you must read the entire KV cache to compute attention, and the arithmetic per byte read is tiny. So decoding time is roughly (bytes of cache) / (HBM bandwidth). Cutting the cache 32x cuts the bytes you stream per token by 32x, which is why MQA’s original motivation (Shazeer, 2019) was decoding speed, not fitting in memory. Candidates who say “MQA saves memory” get half credit; the full answer is that it removes the dominant term in decoder latency.

The math, and what it buys you.

Checking the Arithmetic in the Example:

values, and — so 16.8M and 0.52M are right, and the ratio is exactly 32, the head count. Two caveats about how these numbers are presented, worth stating so you are not caught out: this counts one layer and one sequence, and the is , i.e. keys and values together. Multiply by the layer count and the batch size for the real figure, and by 2 bytes for fp16. For a 32-layer model that 16.8M becomes GB per sequence under MHA and 34 MB under MQA.

Good and bad.

Good: maximum cache reduction, directly a decode-latency win, cheapest possible serving.

Where the Quality Loss Comes From:

With one shared key/value set, all heads compute their scores against the same content representation and differ only in how they project the query. Heads lose the ability to specialize their retrieval basis — you get 32 different questions asked of one index rather than 32 different indexes. The measured effect is a small but consistent perplexity increase and, more visibly, degradation on long-context retrieval tasks. GQA exists precisely because the drop from 32 KV heads to 1 is not smooth: most of the quality is recovered by going back up to 8.

Trade-offs:

  • Maximum memory reduction
  • Slight quality loss compared to MHA
  • Still achieves good quality
  • Used when maximum efficiency needed

Follow-up: What replaced MQA at the frontier? GQA for most open-weights models, and for the very largest context windows, multi-head latent attention (MLA, introduced in DeepSeek-V2) — which compresses K and V into a shared low-rank latent that is cached instead, reaching MQA-like cache sizes with quality closer to full MHA. If you are asked “what’s newer than GQA,” MLA is the answer to name.

Why the interviewer asks this. The good answer mentions memory bandwidth rather than capacity, which is the tell for someone who has profiled a decoder.

Saying it out loud. Multi-query attention takes it to the limit — all the heads share a single set of keys and values, so the cache is as small as it gets, thirty-two times smaller for a thirty-two-head model. The reason that matters isn’t really capacity, it’s bandwidth: generating each token means reading the whole cache from memory, and decoding is bandwidth-bound, so a smaller cache is directly a faster decode. The downside is that heads can no longer specialize what they retrieve against, and you see it on long-context recall. That’s why grouped-query, with something like eight groups, became the compromise everyone settled on.


Q112: What is Paged Attention? How does it improve memory efficiency?

In 30 seconds. “Paged attention borrows virtual memory from operating systems. Instead of reserving one contiguous KV slab per request sized for the worst case, you chop the cache into fixed blocks of about sixteen tokens and hand them out on demand through a per-sequence page table. Waste drops to at most one partial block per sequence — and because it’s a page table, two sequences can share physical blocks.”

The short version.

Contiguous KV cachePaged KV cache
AllocationOne slab per sequence, sized for max lengthFixed blocks (e.g. 16 tokens) on demand
WasteInternal + reservation + external fragmentation; 60-80% measured< 1 block per sequence; under ~4% typical
Utilization~70%95%+
SharingImpossibleCopy-on-write blocks across beams and prefixes
CostPlain Q @ K.TCustom kernel gathering K/V through a block table

Paged Attention:

  • Memory-efficient KV cache management
  • Manages cache in non-contiguous pages (blocks)
  • Similar to virtual memory in operating systems
  • Core innovation behind vLLM

Paged Attention Solution:

1. Page Structure:

  • Divide KV cache into fixed-size pages (blocks)
  • Each page stores K, V for block_size tokens (e.g., 16 tokens)
  • Pages can be non-contiguous in memory

2. Memory Management:

  • Maintain pool of free pages
  • Allocate pages on-demand
  • Return pages to pool when sequence finishes
  • Pages can be reused immediately

Why it works.

The Problem: Memory Fragmentation

Standard KV Cache:

  • Store K, V for each sequence in contiguous memory
  • Variable-length sequences → memory fragmentation
  • When sequence finishes, memory freed but fragmented
  • Cannot reuse efficiently → waste

Example:

Sequence 1: [12 tokens, finished] → 12 tokens freed
Sequence 2: [16 tokens, still generating]
New sequence needs 20 tokens → Cannot use the 12 freed tokens (fragmented)

Naming the Two Kinds of Waste:

Classical KV-cache allocators reserve a contiguous buffer sized to the maximum possible sequence length, because you cannot know in advance how long a generation will be. That produces three separate losses. Internal fragmentation is the unused tail of a reserved buffer — reserve 2048 slots, generate 200 tokens, waste 1848. Reservation waste is space held for tokens that will be generated later but is idle now. External fragmentation is free memory that exists but is not contiguous enough to satisfy a new request. The vLLM paper measured that existing systems wasted 60-80% of KV memory to these three combined; PagedAttention’s own waste is bounded by at most one block per sequence, which at block size 16 and typical sequence lengths is under 4%.

The Part the Answer Above Leaves Out — Sharing:

Because the mapping from logical token positions to physical blocks goes through a per-sequence block table, two sequences can point at the same physical block. That enables two things the contiguous design cannot do at all. Parallel sampling and beam search: candidates from one prompt share the prompt’s blocks with a reference count, and copy-on-write only the block being appended to, so beams cost roughly one prompt’s memory instead of . And prefix caching: a long shared system prompt is stored once and reused across every request in the fleet. In practice the sharing wins are as large as the fragmentation wins, and mentioning them is what distinguishes a real answer from a paraphrase of the abstract.

The math, and what it buys you.

Worked Example with the Block Size:

With block_size = 16, a 25-token sequence occupies blocks holding 32 slots, so 7 slots are idle — 21.9% waste for this short sequence, and it shrinks as the sequence grows: a 1000-token sequence uses 63 blocks (1008 slots) and wastes 8 slots, 0.8%. The bound is always “less than one block,” which is why the average is small even though a short sequence can look bad. Smaller blocks reduce waste but add block-table lookup overhead and hurt kernel efficiency; 16 is the usual compromise.

Example:

  • block_size = 16 tokens
  • Sequence of 25 tokens: needs 2 pages (32 tokens allocated)
  • Waste: Only 7 tokens (within last page)
  • Much better than standard (could waste 50%+)

Memory Efficiency:

  • Standard: ~70% utilization (due to fragmentation)
  • Paged: ~95%+ utilization
  • Enables serving more sequences with same memory

Good and bad.

3. Benefits:

  • No memory fragmentation
  • Efficient memory reuse
  • Can handle variable-length sequences
  • Better GPU memory utilization (95%+ vs ~70%)

What It Costs:

Attention can no longer be one contiguous Q @ K.T; the kernel must gather K and V through the block table, which is why PagedAttention is a custom CUDA kernel rather than a memory-allocator change. There is a small per-token indirection cost, repaid many times over by the higher batch size the saved memory allows — vLLM reported 2-4x throughput over the then-current systems at the same latency.

See 05_attention_mechanisms/advanced_attention_mechanisms.md for complete details!

Note on currency (2026). Paged KV cache is now standard across serving stacks rather than a vLLM-specific feature, and it is routinely combined with prefix caching, chunked prefill, continuous batching, and KV quantization. Specific throughput multipliers from the 2023 paper are baselines against 2023 systems — quote them as historical, not as current speedups.

Follow-up: Does paged attention reduce the total KV cache size? No. It stores the same number of key/value vectors; it changes where they live so that near-100% of the reserved pool is usable and blocks can be shared. Reducing the size per token is GQA/MQA/MLA’s job, and the two are complementary — production stacks use both.

Why the interviewer asks this. It is a systems question wearing a modelling costume, and it separates people who have deployed an LLM from people who have only trained one.

Saying it out loud. Paged attention borrows virtual memory from operating systems. Instead of reserving one contiguous slab of KV cache per request, sized for the worst case, you chop the cache into fixed blocks of about sixteen tokens and hand out blocks on demand through a per-sequence page table. Wasted space drops to at most one partial block per sequence instead of most of a reservation. The part people forget is sharing — because it’s a page table, two sequences can point at the same physical block, so beam search and a shared system prompt cost memory once instead of once per request.


Q113: Compare MHA, GQA, and MQA. When should you use each?

In 30 seconds. “All three keep separate query heads; the only thing that changes is how many distinct key-value sets you cache — one per head, exactly one, or something in between. For a thirty-two-head model that’s a one-times, thirty-two-times, or four-times reduction in cache, and since cache size sets how many requests you can batch, it’s really a throughput dial. Default to grouped-query with eight groups.”

The short version.

Comparison Table:

AspectMHAGQAMQA
Q Projectionsnum_headsnum_headsnum_heads
K Projectionsnum_headsnum_groups1
V Projectionsnum_headsnum_groups1
KV Cachenum_heads × seq_len × (d_k + d_v)num_groups × seq_len × (d_k + d_v)seq_len × (d_k + d_v)
QualityBestVery GoodGood
MemoryHighestMediumLowest
Use CaseTraining, researchProduction (recommended)Maximum efficiency

The Decision Rule in One Line:

Use GQA with between 4 and 8 unless you have a specific reason not to. MHA only if you are training something small where the cache never binds, or replicating a paper. MQA only when the cache is still the binding constraint after GQA — extreme context lengths or extreme batch sizes — and you have measured that the long-context recall loss is acceptable for your task.

Why it works.

How to Read the Table:

Every row is per layer, per sequence. The “Q Projections” row is identical across all three because none of these schemes touches the query side — that is the defining property of the family. The cache rows differ only in the multiplier, versus versus 1, so the whole comparison collapses to a single number: how many distinct key/value head sets do you keep. The parameter savings are real but secondary; on a 70B model, shrinking and from to saves about B parameters, meaningful but not the reason anyone adopts GQA.

The math, and what it buys you.

Example: 32 heads, 8 groups, seq_len=2048

MHA:

  • KV Cache: 32 × 2048 × 256 = 16.8M values
  • Quality: Best
  • Use: Training, maximum quality needed

GQA:

  • KV Cache: 8 × 2048 × 256 = 4.2M values (4× reduction)
  • Quality: Very Good (minimal loss)
  • Use: Production inference (recommended)

MQA:

  • KV Cache: 1 × 2048 × 256 = 0.5M values (32× reduction)
  • Quality: Good (slight loss)
  • Use: Maximum efficiency needed

Rebuilding the Example from First Principles:

Take 32 heads, , seq_len = 2048, 32 layers, fp16, batch of 1:

  • MHA: M values per layer layers bytes GB
  • GQA-8: M per layer MB
  • MQA: M per layer MB

On an 80 GB accelerator holding a 14 GB fp16 7B model, you have roughly 66 GB for cache: about 61 concurrent sequences under MHA, 246 under GQA-8, and 1900 under MQA. Throughput on a serving system is close to linear in concurrency until you saturate compute, so this is directly a throughput table.

Good and bad.

When to Use:

MHA:

  • Training: Maximum quality
  • Research: Need best performance
  • When: Have resources, quality is priority

GQA:

  • Production inference: Best balance
  • Recommended default
  • When: Need efficiency but maintain quality

MQA:

  • Maximum efficiency needed
  • Quality loss acceptable
  • When: Resource-constrained, high throughput

Paged Attention:

  • Can be used with any of above
  • Production serving (vLLM)
  • When: Need efficient memory management

One Correction Worth Making to “MHA: Training”:

The table’s suggestion that MHA is for training is a bit misleading. GQA is used during pretraining in every modern model that ships with it — Llama 2/3, Mistral, Qwen, Gemma — not bolted on afterwards. The uptraining procedure from the GQA paper is for converting existing MHA checkpoints; if you are training from scratch today you choose GQA at the start.

Follow-up: Does GQA slow down training? Essentially no. Training is compute-bound and processes the whole sequence in parallel with no cache, so the KV savings do not help, and the repeat_interleave broadcast adds negligible cost. GQA is close to free at training time and decisive at inference time, which is why it is a pure win.

Saying it out loud. All three keep separate query heads; the only thing that changes is how many distinct key-value sets you cache. Multi-head keeps one per head, multi-query keeps exactly one, and grouped-query picks something in between, usually four or eight. For a thirty-two-head model that’s a one-times, thirty-two-times, or four-times reduction in cache, and since cache size sets how many requests you can batch, it’s really a throughput dial. I’d default to grouped-query with eight groups, and I’d note that paged attention is orthogonal — it manages the cache, it doesn’t shrink it.


See 05_attention_mechanisms/advanced_attention_mechanisms.md for complete theory! See 05_attention_mechanisms/advanced_attention_code.py for complete code!


Mixture of Experts (MoE)

Q114: What is Mixture of Experts? How does it work?

In 30 seconds. “A mixture-of-experts layer replaces one feed-forward block with several copies, plus a tiny linear router that scores each of them for every token. You keep the top one or two, run only those, and combine their outputs weighted by the router’s scores. So the model holds a lot of parameters but only touches a slice per token — and routing is per token and per layer, not per sentence.”

The short version.

  • Multiple experts: 8-128 feed-forward networks, independent, same architecture.
  • Router: takes the token’s hidden state, outputs expert scores, selects top-.
  • Sparse activation: only experts run per token (typically =1 or 2); most stay inactive.
  • Weighted combination: outputs of the selected experts, weighted by router scores.
  • Efficiency: total params = num_experts × params_per_expert; active = × params_per_expert. 8 experts, =2 → 4× reduction in computation.

Mixture of Experts (MoE):

  • Architecture with multiple expert networks
  • Router decides which experts to activate
  • Only subset of experts process each input
  • Enables models with trillions of parameters

How It Works:

1. Multiple Experts:

  • 8-128 feed-forward networks
  • Each expert is independent
  • All experts have same architecture

2. Router:

  • Takes input, outputs expert scores
  • Computes probability distribution
  • Selects top-k experts with highest scores

3. Sparse Activation:

  • Only k experts activated per token (typically k=1 or 2)
  • Most experts remain inactive
  • Reduces computation significantly

4. Weighted Combination:

  • Process through selected experts
  • Weighted combination of outputs

Efficiency:

  • Total parameters: num_experts × params_per_expert
  • Active parameters: k × params_per_expert
  • Example: 8 experts, k=2 → 4× reduction in computation

Why it works.

What Is and Is Not Replaced:

An important detail the summary skips: in a modern LLM the experts replace the feed-forward block only. Attention, layer norms and embeddings stay dense and shared. This matters because the FFN is roughly two thirds of a transformer’s parameters, so replicating it 8x does not make the model 8x bigger. It also explains why MoE is applied where it is: the FFN is a per-token, position-independent function, so routing each token to a different copy is coherent, whereas routing tokens to different attention blocks would break the mixing that attention exists to do.

The math, and what it buys you.

The Router, Concretely:

The router is a single linear layer — for and experts that is 32,768 parameters, utterly negligible. For each token’s hidden state :

Two things to notice. Routing is per token, not per sequence — the tokens of one sentence scatter across many experts, and the assignment changes at every layer, so a token’s path through a 32-layer top-2 MoE is one of possible routes. And the gate weights multiply the expert outputs, which is the only path by which gradient reaches at all: top-k is not differentiable, so the router learns solely through the magnitude of the weights on the experts it did select.

Worked Shapes for Mixtral-8x7B:

, , SwiGLU (three matrices: gate, up, down), 32 layers, 8 experts, top-2. Per expert per layer: M parameters. All 8 experts, all 32 layers: B. Add GQA attention (B) and embeddings (B) and you land at roughly 46.7B total. Active per token: B of FFN plus the shared 1.3B attention B.

Good and bad.

Good: huge parameter count at small per-token compute; the router costs essentially nothing; enables trillion-parameter models.

Bad: memory scales with all experts, not the active ones; routing is non-differentiable so training is delicate; load imbalance is a standing failure mode (see Q116).

Follow-up: Why top-2 rather than top-1? With the gate weight is always 1 after renormalization, so the router gets almost no useful gradient and training is unstable — Switch Transformer needed extra tricks to make work. With the two weights compete, giving a real gradient signal, at double the FFN compute. Most production models use ; some very sparse designs use out of 256 fine-grained experts.

Why the interviewer asks this. Nearly every frontier model shipped since 2024 is sparse, so MoE has moved from exotic to table stakes.

Saying it out loud. A mixture-of-experts layer replaces one feed-forward block with several copies, plus a tiny linear router that scores each of them for every token. You keep the top one or two, run only those, and combine their outputs weighted by the router’s scores. So the model holds a lot of parameters but only touches a slice of them per token. The routing is per token and per layer, not per sentence, so words in the same sentence take completely different paths through the network.


Q115: How does MoE reduce computation? Compare with dense models.

In 30 seconds. “The saving is in compute per token, not memory. Mixtral holds about forty-seven billion parameters but only runs about thirteen billion for any given token — the compute cost of a small model with the memory footprint of a large one. And the real speedup is well under the four-times you’d predict, because attention is unchanged and the all-to-all between experts isn’t free.”

The short version.

Dense modelMoE model
Parameters used per inputall of experts
Compute per tokenO(d_model²)O( × d_model²)
Memory= active params= total params
7B example7B params, all active8 experts × 7B stored, 2 × 7B active
Binding constraintcomputeVRAM

Dense Model:

  • All parameters used for every input
  • Computation: O(d_model²) per token
  • Example: 7B parameters, all active

MoE Model:

  • Total: num_experts × params_per_expert
  • Active: k × params_per_expert
  • Computation: O(k × d_model²) per token

Example: Mixtral-8x7B

  • 8 experts × 7B = 56B total parameters
  • k=2 → 2 × 7B = 14B active per token
  • Computation: Only 14B parameters (not 56B!)

Trade-off:

  • More parameters (memory)
  • Less computation (speed)
  • Best of both worlds

Correction to the Mixtral numbers above. Mixtral-8x7B is not B total with 14B active. The name is misleading: only the feed-forward blocks are replicated, while attention, embeddings and norms are shared across experts. The published figures are 46.7B total parameters and about 12.9B active per token. The arithmetic in the previous question reconstructs both. Getting this right matters in an interview because the “8x7 = 56” mistake is exactly what someone who has only read the model name would say.

Why it works.

Where the Savings Actually Land:

Per token, FLOPs scale with (experts used), while memory scales with (experts stored). For Mixtral: FLOPs are those of a ~12.9B dense model, memory is that of a ~46.7B dense model. So the honest framing is that MoE buys you the quality of a large model at the compute of a small one, and you pay in VRAM. On an 80 GB accelerator, Mixtral at fp16 needs 93 GB and does not fit on one card — the compute saving is real but you needed two GPUs to get it.

The math, and what it buys you.

Reduction:

  • Computation: (num_experts / k)× reduction
  • 8 experts, k=2 → 4× reduction
  • But total parameters: 8× more

The Scaling-Law Framing:

The useful way to state the trade is: at a fixed training FLOP budget, a sparse model reaches a lower loss than a dense one, because it has more parameters to store knowledge in while activating the same number per token. That is why the frontier went sparse. The cost is that inference memory, not inference compute, becomes the binding constraint — which is precisely the constraint that MoE-specific serving work (expert offloading, expert-parallel routing, expert caching) exists to attack.

Good and bad.

Why the Compute Saving Is Less Than the Ratio Suggests:

The naive claim is compute reduction versus a dense model with the same total parameters. Three things erode it. Attention is unchanged and is a large share of FLOPs at long context, so total speedup is well under the FFN-only ratio. Sparse dispatch means each expert receives a scattered subset of tokens, so you pay a gather/scatter and get worse matmul shapes than one big dense GEMM. And at scale experts live on different devices, so every MoE layer contains two all-to-all collectives (dispatch and combine) whose cost is network-bound and does not shrink with . Real end-to-end MoE speedups at matched quality are meaningful but nowhere near .

Follow-up: Is Mixtral as good as a dense 46.7B model? No — roughly, a sparse model behaves like a dense model of size near the geometric mean of its total and active parameters. Mixtral benchmarks around or above a dense 13B and competitively with much larger dense models on many tasks, but it is not a 47B dense model with 13B’s compute for free.

Saying it out loud. The saving is in compute per token, not in memory. Mixtral holds about forty-seven billion parameters but only runs about thirteen billion of them for any given token, so you get the compute cost of a small model and the memory footprint of a large one. And notice the name is misleading — eight times seven isn’t fifty-six, because only the feed-forward blocks are duplicated and everything else is shared. The other thing I’d flag is that the real speedup is well under the four-times you’d predict, because attention is unchanged and the all-to-all communication between experts isn’t free.


Q116: What is load balancing in MoE? Why is it important?

In 30 seconds. “Left alone, routers collapse — an expert that gets a few more tokens early trains faster, gets scored higher, and takes even more, until most experts are dead weight. So you add an auxiliary loss pushing routing probabilities toward uniform, weighted by how overloaded each expert already is. It matters more than it sounds: experts have fixed capacity, and tokens routed to a full expert are silently dropped.”

The short version.

  • The failure: router keeps picking the same experts; the rest are never used. Expert collapse.
  • The classic fix: an auxiliary loss penalizing uneven usage, .
  • The hidden cost of imbalance: overflow tokens are dropped, skipping the FFN entirely.
  • The systems cost: under expert parallelism, step time is set by the busiest device.
  • Current practice: router z-loss for stability; auxiliary-loss-free bias-nudging (DeepSeek-V3).

Why it works.

Load Balancing Problem:

  • Without balancing, router might always select same experts
  • Some experts never used (waste)
  • Others overloaded (bottleneck)
  • Expert collapse: Only few experts ever used

Goal:

  • Minimize variance of expert usage
  • Distribute tokens evenly
  • All experts used roughly equally

Why Important:

  • Without: Experts 0-2 always used, 3-7 never used
  • With: All experts used equally
  • Better parameter utilization
  • Prevents expert collapse

Why Imbalance Is a Systems Problem, Not Just a Quality Problem:

Each expert is given a fixed capacitycapacity = capacity_factor × tokens_per_batch / E — because the dispatch buffers must be statically shaped for the all-to-all collective. Tokens routed to an expert that is already full are dropped: they skip the FFN entirely and pass through on the residual connection only. So imbalance does not just underuse parameters; it silently deletes computation for real tokens. A capacity factor of 1.25 is common, which means you are provisioning 25% headroom and still dropping tokens whenever routing is skewed. And under expert parallelism the step time is set by the slowest expert’s device, so a 2x-overloaded expert doubles your step time no matter how idle the other seven GPUs are.

The math, and what it buys you.

Solution: Load Balancing Loss

L_balance = (1/num_experts) * sum(load_i)²

Where load_i is fraction of tokens routed to expert i.

Correction to the loss formula above. As written, cannot train the router: is a count of tokens routed to expert , produced by a top-k operation, and counts have no gradient. The Switch Transformer formulation fixes this by pairing the count with the router’s soft probability:

where is the fraction of tokens dispatched to expert (non-differentiable, treated as a constant) and is the mean router probability assigned to expert over the batch (differentiable). The gradient flows through , and multiplying by scales the penalty by how overloaded that expert already is: if expert 3 is taking 40% of the tokens, the loss pushes down hard. The factor makes the minimum value 1 at perfect balance regardless of expert count, and is typically .

Worked Numbers:

With and perfect balance, , so . Now suppose one expert takes half the tokens and the router agrees () while the other seven split the rest evenly (): . Balanced gives 1.0, collapsed gives 2.29, and the maximum, total collapse onto one expert, is . So the auxiliary loss ranges over and you can read the number directly as “how many times worse than balanced am I.”

Good and bad.

Two Refinements Worth Naming (current practice, 2026):

Router z-loss, , penalizes large router logits and is what actually stabilizes large MoE training in bf16 — imbalance is not the only pathology, logit blowup is the other. And auxiliary-loss-free balancing, introduced with DeepSeek-V3, drops the aux loss entirely in favour of a per-expert bias added to the routing scores, nudged up or down after each step based on observed load. Because the bias affects only selection and not the gate weights used in the output, it balances load without adding a gradient term that fights the language-modelling objective — which is the standing complaint against auxiliary losses. Expect this to be the answer an up-to-date interviewer is listening for.

Follow-up: What is expert collapse and why does it happen? It is a rich-get-richer feedback loop: an expert that receives slightly more tokens early trains slightly faster, becomes slightly better, so the router scores it higher, so it receives more tokens. Without an explicit counterweight the system converges to a handful of used experts, and the rest are dead parameters occupying VRAM.

Why the interviewer asks this. Load balancing is where MoE stops being an architecture diagram and becomes a distributed-systems problem, so it is a fast way to tell whether you have trained one or read about one.

Saying it out loud. Left alone, routers collapse — an expert that gets a few more tokens early trains faster, gets scored higher, and takes even more, until most experts are dead weight. So you add an auxiliary loss that pushes the routing probabilities toward uniform, weighted by how overloaded each expert already is. It matters more than it sounds, because experts have a fixed capacity and tokens routed to a full expert are just dropped, and because under expert parallelism your step time is set by the busiest GPU. The newer approach, from DeepSeek-V3, skips the extra loss and just nudges a per-expert bias based on observed load.


See 41_mixture_of_experts/moe_theory.md for complete theory! See 41_mixture_of_experts/moe_code.py for complete code! See 41_mixture_of_experts/moe_qa.md for comprehensive Q&A!


State Space Models (SSM)

Q117: What are State Space Models? How do they work?

In 30 seconds. “A state space model keeps a fixed-size hidden state and updates it linearly at each step — new state is A times old state plus B times the input, output reads off through C. Because that update is linear and time-invariant, you can unroll it into a convolution and train the whole sequence in parallel, then switch to the recurrent form for generation, where it’s constant time and constant memory per token.”

The short version.

Transformer attentionState space model
Per-step cost (attends to all previous)
Total sequence cost
Generation memoryKV cache grows with Fixed -dim state
Training formParallel by constructionConvolution (FFT),
Memory of the pastExact, random accessLossy fixed-size compression

State Space Models (SSMs):

  • Sequence models using hidden state
  • Process sequences with linear recurrence
  • O(n) complexity (vs O(n²) for transformers)
  • Better for very long sequences

How They Work:

1. Hidden State:

  • Maintain state h[k] that evolves over time
  • State captures information from all previous inputs
  • Updated at each step

**2. State Evolution:

h[k+1] = A_d h[k] + B_d u[k]  # State update
y[k] = C_d h[k] + D_d u[k]    # Output

3. Linear Recurrence:

  • Each step: O(1) computation
  • Total: O(n) for sequence of length n
  • Much faster than attention: O(n²)

Key Insight:

  • State summarizes past information
  • Don’t need to attend to all previous tokens
  • More efficient than attention

Why it works.

The Two Modes — This Is the Whole Trick:

Because the recurrence is linear and (in a classical SSM) time-invariant, it can be unrolled into a convolution. Substituting repeatedly gives with kernel

So the same parameters give you two computational forms: a recurrent mode, time and memory per step, ideal for autoregressive generation; and a convolutional mode, computable by FFT in and fully parallel over the sequence, ideal for training. You train with the convolution and generate with the recurrence. Without that duality an SSM would be as slow to train as an RNN, and nobody would use it.

Why Cannot Be Random:

Naively initializing randomly produces a model that fails badly — the state either explodes or forgets within a few steps. S4’s contribution was to initialize with a HiPPO matrix, derived so that the state maintains an optimal polynomial-basis approximation of the entire input history. The structure of is doing the memory work, and this is the single most common gap in a candidate’s answer: they describe the recurrence and omit that a generic linear recurrence does not work.

The math, and what it buys you.

Where the Discrete Equations Come From:

The equations above are the discretized form. The underlying object is a continuous linear ODE borrowed from control theory:

To run it on a token sequence you discretize with a step size , usually zero-order hold, which gives and . Those bars are why the discrete recurrence uses . The reason to care is that becomes a learnable parameter with a real interpretation — it is the model’s timescale, controlling how fast the state forgets. A large means “pay attention to the current input”; a small means “ignore this input and hold the state.”

Good and bad.

Good:

  • Sequence models using hidden state, processed with linear recurrence
  • O(n) complexity (vs O(n²) for transformers)
  • Better for very long sequences
  • Each step: O(1) computation; total O(n) for a sequence of length n

The Fixed-Size State Is the Point and the Limitation:

A transformer’s KV cache grows with sequence length — at step it holds key/value pairs. An SSM’s state is a fixed -dimensional vector (typically per channel) regardless of whether you have seen 100 tokens or 100,000. Constant memory during generation is the headline benefit. The flip side is that everything the model remembers must be compressed into those numbers, so exact recall of an arbitrary earlier token is impossible in a way it simply is not for attention.

Follow-up: How is this different from an LSTM? An LSTM’s recurrence is nonlinear (gates applied to the hidden state), so it cannot be unrolled into a convolution or a parallel scan and must be trained strictly sequentially. SSMs deliberately keep the state update linear in , which is what buys parallel training. Nonlinearity is reintroduced between layers instead of inside the recurrence.

Why the interviewer asks this. Whether you can explain the recurrent/convolutional duality is a clean test of whether you understand SSMs or have only memorized “linear instead of quadratic.”

Saying it out loud. A state space model keeps a fixed-size hidden state and updates it linearly at each step — new state is A times old state plus B times the input, and the output reads off through C. Because that update is linear and time-invariant, you can unroll it into a convolution and train the whole sequence in parallel with an FFT, then switch to the recurrent form for generation, where it’s constant time and constant memory per token. The catch is that the memory is a fixed-size vector, so it compresses history rather than storing it, and the A matrix has to be initialized with special structure or it doesn’t learn at all.


Q118: What is Mamba? How does it differ from standard SSMs?

In 30 seconds. “A classical SSM applies the same fixed filter to every token, so it can’t decide that one word is worth remembering and another isn’t. Mamba makes the input matrix, the output matrix, and — most importantly — the step size depend on the current token, so the forget rate becomes content-aware. That’s a gate, expressed in a way that keeps the recurrence linear.”

The short version.

Standard SSM (S4)Mamba (selective SSM)
fixed, time-invariant, , and are functions of the input
Content-based reasoningimpossiblethis is the whole point
Training formconvolution / FFTparallel associative scan (no kernel exists)
Diagnostic tasksfails selective copying, induction headssolves both

Mamba:

  • Selective State Space Model
  • Makes parameters input-dependent
  • More expressive than fixed SSMs
  • State-of-the-art for long sequences

Key Difference:

Standard SSM:

h[k+1] = A h[k] + B u[k]  # Fixed A, B
y[k] = C h[k]             # Fixed C

Mamba (Selective):

B[k] = Linear_B(u[k])  # Input-dependent B
C[k] = Linear_C(u[k])  # Input-dependent C
h[k+1] = A h[k] + B[k] u[k]
y[k] = C[k] h[k]

Why This Works:

  • Different inputs need different transitions
  • B[k] controls how input affects state
  • C[k] controls what to extract
  • More expressive while maintaining O(n) complexity

Why it works.

The Parameter the Summary Above Omits — and It Is the Important One:

Mamba makes , and input-dependent. is the discretization step, and since , making a function of the input makes the forget rate itself content-dependent. Large for a token means “this matters — reset toward it”; small means “filler, hold the state.” That is the actual selection mechanism, and it is the direct analogue of an LSTM forget gate reintroduced in a form that still admits parallel training. Naming only and is a half answer.

Why Selectivity Was Necessary:

A time-invariant SSM applies the same filter to every token, so it cannot do content-based reasoning — it cannot decide to remember this name and discard that adjective. The paper’s diagnostic tasks make this concrete: selective copying (copy tokens while ignoring randomly interspersed noise) and induction heads (see “A B … A”, predict “B”) are unsolvable for S4 and solved by Mamba, because both require the recurrence to condition on content.

The math, and what it buys you.

Concrete Shapes:

For with the usual expansion factor 2, the inner dimension and the state size is per channel, so the materialized state is values per layer per sequence — constant in sequence length. Compare that with a transformer layer’s KV cache at 32k context: M values under GQA, a factor of roughly 1000. That ratio is the entire commercial argument for SSMs.

Good and bad.

Good: input-dependent parameters make it far more expressive than a fixed SSM while staying ; state stays constant-size; state of the art on long sequences.

What Selectivity Breaks:

The moment , , depend on the input, the system stops being time-invariant — differs at every position — and the convolutional form from the previous question no longer exists. There is no fixed kernel to FFT. This is the central engineering problem Mamba had to solve, and the solution is a hardware-aware parallel scan (an associative scan over the sequence, work and depth), plus kernel fusion that keeps the expanded state in SRAM and recomputes it in the backward pass rather than writing -shaped intermediates to HBM. The paper’s speed comes as much from that memory-movement design as from the architecture.

Note on currency (2026). Mamba-2 recast the selective SSM as “structured state space duality,” showing the scan is equivalent to a form of masked linear attention and letting it reuse matmul-shaped kernels for a large speedup; a further iteration, Mamba-3, has since continued the line. Verify which generation a paper or job description means before quoting specifics.

Follow-up: If selectivity is just gating, why not use an LSTM? Because Mamba’s state update remains linear in — the input-dependence enters through coefficients, not through a nonlinearity applied to the state. Linearity in is exactly what makes the recurrence associative, and associativity is what makes the parallel scan possible. An LSTM’s gate multiplies a sigmoid of the previous state, which destroys that property.

Saying it out loud. A classical state space model applies the same fixed filter to every token, so it can’t decide that one word is worth remembering and another isn’t. Mamba makes the input matrix, the output matrix, and — most importantly — the step size depend on the current token, so the forget rate becomes content-aware. That’s basically a gate, but expressed in a way that keeps the recurrence linear. The price is that you lose the convolutional training shortcut, so they wrote a hardware-aware parallel scan that keeps everything in fast on-chip memory instead.


Q119: Compare SSMs (Mamba) with Transformers. When to use each?

In 30 seconds. “Transformers cost quadratic time and their KV cache grows with context, so per-token decoding slows down the longer you go. SSMs are linear with a fixed-size state, so decoding cost is flat forever. The catch is that a fixed state is a lossy summary, so they’re noticeably worse at exact recall — which is why almost nobody ships a pure one; the winning pattern is hybrids.”

The short version.

Complexity:

AspectTransformerSSM (Mamba)
TimeO(n²d)O(nd)
SpaceO(n²)O(nd)
ScalingQuadraticLinear

When to Use:

Transformers:

  • Short-medium sequences (< 8K tokens)
  • Need maximum quality
  • Established architecture

SSMs (Mamba):

  • Very long sequences (> 8K tokens)
  • Need efficiency
  • Sequences of length 100K+

Crossover:

  • < 2K: Transformers faster
  • 8K: SSMs faster

  • 100K: SSMs much better

Caution on the crossover numbers. The “< 2K transformers faster, > 8K SSMs faster” thresholds in the answer above are indicative, not physical constants. They depend on hidden size, whether FlashAttention or a fused scan kernel is used, GPU memory bandwidth, batch size, and whether you are measuring training or decoding. Treat them as an order of magnitude and say so — an interviewer will respect “it depends on the kernel and the hardware, roughly a few thousand tokens” more than a confident wrong number.

Why it works.

Reading the Complexity Table Properly:

The for transformers is the attention term only; the feed-forward blocks are and dominate until . For , attention is not the largest cost until sequences exceed a few thousand tokens, which is why short-context transformers feel fine. At the picture inverts completely. So the honest statement is that attention’s quadratic term is irrelevant at small and decisive at large , and “SSMs are linear” only cashes out past that crossover.

The Inference Asymmetry Is Bigger Than the Training One:

Per generated token, a transformer must read a KV cache that grows linearly with context, so decoding cost per token increases as the sequence lengthens. An SSM reads a fixed-size state, so its per-token cost is flat forever. That means the gap is not a constant factor, it widens without bound: at 1M tokens of context an SSM still decodes at its 100-token speed. For streaming or very-long-document workloads this, not training FLOPs, is the reason to care.

Good and bad.

The Real Weakness, Stated Honestly:

Because the state is a fixed-size compression, SSMs are measurably worse at tasks requiring exact retrieval from context — copying a long string verbatim, needle-in-a-haystack lookups, following a many-shot in-context pattern. Attention has, in effect, lossless random access to every previous token; an SSM has a lossy summary. This is not a training deficiency, it is an information-theoretic consequence of the architecture, and it is the honest answer to “why hasn’t Mamba replaced transformers.”

Note on where the field landed (2026 — the most time-sensitive claim in this section). The practical answer is neither pure architecture but hybrids: stacks that are mostly SSM layers with a small fraction of full-attention layers interleaved, typically something like one attention layer per six or eight SSM layers. Jamba, Nemotron-H, Falcon-H1 and Zamba all follow this pattern, and the empirical finding is consistent — a few attention layers recover almost all of the retrieval ability while the SSM layers keep most of the memory and throughput advantage. If asked “SSM or transformer,” the strongest current answer is “hybrid, and here is why the ratio is what it is.” Specific model names and ratios in this area date quickly; check current releases before quoting them.

Follow-up: Can you convert a trained transformer into an SSM? Yes, approximately — distillation approaches initialize SSM layers from the attention weights of a pretrained transformer and fine-tune on a small fraction of the original data, which is far cheaper than pretraining a Mamba model from scratch. Quality lands below the teacher but above training the same SSM from scratch on the same budget.

Why the interviewer asks this. They want to hear a trade-off with a named failure mode, not architecture cheerleading — “SSMs are linear so they’re better” is the answer that fails.

Saying it out loud. Transformers cost quadratic time in sequence length and their KV cache grows with context, so per-token decoding gets slower the longer you go. State space models are linear and keep a fixed-size state, so decoding cost is flat no matter how long the context. The catch is that a fixed state is a lossy summary, so they’re noticeably worse at exact recall — copying a string, finding a needle in a haystack. Which is why almost nobody ships a pure one; the winning pattern right now is hybrids, mostly SSM layers with an attention layer sprinkled in every six or eight, which recovers the retrieval and keeps most of the speed.


See 42_state_space_models/ssm_theory.md for complete theory! See 42_state_space_models/ssm_code.py for complete code! See 42_state_space_models/ssm_qa.md for comprehensive Q&A!

Classical ML: Trees, Ensembles, and Dimensionality

Q120: How does a decision tree decide where to split?

In 30 seconds. “At each node the tree tries every feature and every threshold, scores each candidate by how much it drops impurity — Gini or entropy — weighted by how many samples go to each side, takes the best, and recurses. Gini is the probability two random samples in the node disagree; entropy is the bits needed to encode the label. They almost always pick the same split.”

The short version.

  • Splits are axis-aligned: “feature is less than threshold .”
  • Training is greedy: enumerate, score by impurity drop, take the best, recurse.
  • Impurity is zero for a pure node, maximal for an even mixture.
  • The drop must be weighted by child sizes, or peeling off one pure sample looks brilliant.
  • Regression trees use variance (sum of squared errors) as the impurity; everything else is identical.

Why it works.

A decision tree is a model that repeatedly partitions the feature space with axis-aligned cuts of the form “feature is less than threshold .” Training is greedy: at every node the algorithm enumerates candidate splits, scores each one by how much it reduces an impurity measure, and takes the best. Impurity is a number that is zero when a node contains one class only and maximal when the classes are evenly mixed.

Gini and entropy almost always pick the same split. Entropy is slightly more sensitive to changes near the pure end because blows up there, so it has a mild preference for splits that carve off pure regions; Gini is cheaper because it avoids logarithms. In practice the choice is worth less than one hyperparameter tick of max_depth.

The math, and what it buys you.

The two standard impurity measures for classification. Let be the fraction of samples at a node belonging to class .

Gini is the probability that two samples drawn at random from the node have different labels. Entropy is the expected number of bits needed to encode a label drawn from the node. Both peak at even mixtures — for two classes, Gini peaks at and entropy at bit.

A split sends samples left and right out of total. Its score is the drop in impurity, weighted by how many samples land in each child:

With entropy this quantity is called information gain. The weighting matters — without it, a split that peels off a single pure sample would look fantastic.

Worked arithmetic. Ten samples, five positive and five negative, so the parent has and bit. Compare two candidate splits.

Split A — left gets 4 positive and 1 negative, right gets 1 positive and 4 negative. Split B — left gets 5 positive and 2 negative, right gets 0 positive and 3 negative (a pure leaf).

Running the arithmetic:

def gini(counts):
    n = sum(counts)
    p = [c / n for c in counts]
    return 1 - sum(x * x for x in p)
leftrightweighted GiniGini gainweighted entropyinfo gain
Split A0.32000.32000.32000.18000.72190.2781
Split B0.40820.00000.28570.21430.60420.3958

Verified output from the script:

parent gini 0.5 ent 1.0
A L [4, 1] R [1, 4] weighted gini 0.32   gini gain 0.18   weighted ent 0.7219 info gain 0.2781
B L [5, 2] R [0, 3] weighted gini 0.2857 gini gain 0.2143 weighted ent 0.6042 info gain 0.3958

Split B wins on both criteria, even though its left child is less pure than either of A’s children, because it manufactures one perfectly pure node and pure nodes are terminal — no further work needed on those three samples.

Good and bad.

Two details that interviewers probe. First, the search over thresholds is done by sorting each feature and sweeping the split point, updating class counts incrementally, so scoring all thresholds for one feature costs after an sort — not . Second, information gain is biased toward high-cardinality features: a customer-ID column splits every node perfectly and gains a full bit, while generalizing not at all. The classical fix is gain ratio, which divides information gain by the entropy of the split itself (the “split information”), penalizing splits with many small branches.

Follow-up: Why not just optimize accuracy at each split? → Misclassification rate is piecewise-linear in , so it is often flat: a split can move probability mass in a genuinely useful direction and produce exactly zero change in accuracy, giving the greedy search no gradient to follow. Gini and entropy are strictly concave, so almost any purifying split registers as an improvement.

Why the interviewer asks this. They want to know whether you understand trees as an optimization procedure with a concrete objective, or only as a diagram you have seen in slides.

Saying it out loud. “At each node the tree tries every feature and every threshold, and scores each candidate by how much it drops impurity — Gini or entropy — weighted by how many samples go to each side. Then it takes the best one and recurses. Gini’s the probability two random samples in the node disagree; entropy’s the bits you need to encode the label. They basically always pick the same split, and Gini’s cheaper because there’s no log. The reason we don’t just use accuracy is that accuracy is flat over big regions, so the greedy search gets no signal.”


Q121: Bagging vs boosting — what is actually different, and why does each reduce error?

In 30 seconds. “Both are ensemble methods that combine multiple models, but they attack different components of total error: Bagging reduces Variance by averaging independent models trained in parallel, while Boosting reduces Bias by sequentially training models where each new model fixes the errors of the previous ones.”

Core Differences.

BaggingBoosting
Primary goalReduce varianceReduce bias
Model dependencyIndependent — trained in parallelSequential — each model depends on the last
Base modelsDeep, low-bias, high-variance (unpruned trees)Shallow, high-bias, low-variance (stumps, depth-3)
AggregationAverage / majority vote, equal weightWeighted sum with a small learning rate

Why each reduces error.

Bagging (bootstrap aggregating) trains models independently, each on a bootstrap resample — a sample of rows drawn with replacement from the training rows — and averages their predictions. Because the models are independent given the data, training is embarrassingly parallel. Each base model is deliberately low-bias and high-variance: a fully grown, unpruned tree. Averaging then knocks the variance down. Think of it as polling fifty independent experts: each one’s individual wild guesses cancel out, and what survives the averaging is the signal they agree on.

Boosting trains models sequentially, each one fit to the errors the current ensemble is still making, and adds them up with a small step size. Each base model is deliberately high-bias — a stump or depth-3 tree — and the sequence of corrections drives bias down. It is a student who studies only the questions they got wrong: every round is spent on the part of the material the ensemble has not mastered. Training cannot be parallelized across rounds, because round needs the predictions of rounds .

Why boosting reduces error, in contrast. Boosting is doing stagewise gradient descent in function space (see Q122). Each round fits the residual, so the ensemble’s bias shrinks monotonically on the training set. Variance is controlled indirectly, by the learning rate and by keeping the base learners weak. The consequence: boosting can overfit if you keep adding rounds, and the number of rounds is a genuine regularization hyperparameter that must be tuned with early stopping. This is the single sharpest practical difference to state — more trees never hurts a random forest, more rounds absolutely can hurt a boosted model.

The math, and what it buys you.

What the variance formula tells you is where the ceiling is: adding trees kills one term completely, but leaves a floor set entirely by how correlated the trees are — and that floor is the reason random forests randomize the features considered at every split.

Suppose you average predictors, each with variance , with pairwise correlation between any two of them. Variance of the average:

Read that formula carefully, because it explains the entire design of random forests. The second term vanishes as — more trees always helps, and never hurts, which is why “number of trees” is not really a regularization knob. But the first term, , does not depend on at all. It is a floor. Adding trees past the point where is small buys you nothing.

Numerically, with and :

B=    1 Var=1.0000
B=    5 Var=0.6000
B=   10 Var=0.5500
B=   50 Var=0.5100
B= 1000 Var=0.5005
limit -> 0.5

An empirical simulation with 200,000 draws and gives measured single-model variance , measured pairwise correlation , and measured variance of the average against the predicted . The formula holds.

So the only way to get below the floor is to reduce . That is precisely what a random forest adds on top of bagging: at every split, consider only a random subset of features (max_features, classically for classification). This makes individual trees slightly worse — goes up a little — in exchange for a large drop in , and the product falls. Bagging alone leaves trees highly correlated because one dominant feature gets chosen at the root of nearly every tree.

Good and bad.

Bagging / random forest. Good: robust to noisy labels; forgiving to tune; embarrassingly parallel; more trees never hurts, so you can’t overshoot. Bad: cannot reduce bias, so a forest of weak learners stays weak; the floor caps how far averaging can take you; generally a step behind tuned boosting on clean tabular data.

Boosting. Good: drives bias down monotonically; usually the best accuracy on tabular problems; the loss is swappable, so ranking and quantile objectives come free. Bad: sequential, so no cross-round parallelism; will chase label noise; learning rate, depth and round count interact, so tuning takes care; needs early stopping or it overfits.

The full comparison, row by row:

Bagging / random forestBoosting
Base learnerdeep, low-bias, high-varianceshallow, high-bias, low-variance
Fitted tobootstrap resample of dataresiduals / gradients of current ensemble
Dependencyindependent, parallelsequential
Primarily reducesvariancebias
More modelsnever hurtscan overfit — tune with early stopping
Noisy labelsrobustsensitive (chases the noise)
Tuningforgivingneeds care (lr, depth, rounds interact)

Follow-up: Is boosting immune to variance reduction? → No. Stochastic gradient boosting subsamples rows (subsample) and columns (colsample_bytree) per round, which decorrelates the trees and gives boosting a bagging-like variance benefit on top of its bias reduction. This is why subsample=0.8 is a near-universal default.

Why the interviewer asks this. The bias-variance framing is the standard test of whether a candidate reasons about generalization from first principles, and the formula is the specific thing that separates people who have derived it from people who have memorized “bagging reduces variance.”

Saying it out loud. “Bagging trains models independently on bootstrap samples and averages them — that kills variance. Boosting trains them sequentially, each one fitting the previous ensemble’s residuals — that kills bias. The formula I keep in my head is that averaging correlated predictors gives you rho-sigma-squared plus one-minus-rho over B, times sigma squared. The second term goes to zero with more trees, but the first term is a floor set by how correlated the trees are. That’s the whole reason random forests randomize the features at each split — you’re buying a lower rho. And the practical difference: more trees never hurts a forest, but more boosting rounds definitely can, so you early-stop.”


Q122: How does gradient boosting work, step by step?

In 30 seconds. “Gradient boosting is gradient descent where the parameters are the predictions themselves. Start with a constant, compute the negative gradient of the loss at every training point, fit a small regression tree to those gradients, add it in with a small learning rate, repeat. For squared error the gradient literally is the residual — which is why people say it fits residuals, and why that phrasing is misleading in general.”

The short version.

  1. Initialize with the constant that minimizes the loss: . For squared error that is the mean of ; for log loss it is the log-odds of the base rate.
  2. For : a. Compute the negative gradient (the “pseudo-residual”) for every training point: . b. Fit a regression tree to the pairs — note it is always a regression tree, even for classification, because you are regressing on gradients. c. Optionally, re-solve for the optimal constant in each leaf under the true loss (a line search per leaf) rather than using the tree’s own mean. d. Update: , where is the learning rate, typically to .
  3. Output .

Why it works.

Ordinary gradient descent updates parameters: . Gradient boosting updates the predictions: it treats the vector of model outputs as the free variables and takes a step in the direction . The catch is that a step in prediction-space only tells you how to move on the training points, and you need a function defined everywhere. So you fit a regression tree to the negative gradient and use that tree as your step direction. That’s it. That reframing is the whole idea and it is what the interviewer wants to hear.

For squared error , the negative gradient is exactly — the ordinary residual. That is why the textbook explanation “each tree fits the residuals” is right for regression and misleading in general. For log loss with , the negative gradient is , the residual in probability space. The gradient framing is what lets you swap in Huber loss, quantile loss, or a ranking objective without changing any other machinery.

The math, and what it buys you.

Worked example, executed. Six points, , , depth-1 trees (stumps), learning rate :

F0 = 7.5   MSE = 20.25
round 1: residuals=[-5.5 -4.5 -2.5  1.5  4.5  6.5] split x<3.5, leaves=(-4.167, 4.167)
         -> F=[5.417 5.417 5.417 9.583 9.583 9.583]   MSE=7.2292
round 2: residuals=[-3.417 -2.417 -0.417 -0.583  2.417  4.417] split x<4.5, leaves=(-1.708, 3.417)
         -> F=[4.562 4.562 4.562 8.729 11.292 11.292]  MSE=2.8516
round 3: residuals=[-2.562 -1.562  0.438  0.271  0.708  2.708] split x<2.5, leaves=(-2.062, 1.031)
         -> F=[3.531 3.531 5.078 9.245 11.807 11.807]  MSE=1.2563

MSE falls . Notice the residuals shrink but do not vanish, because deliberately takes half-steps. Setting would fit the training data faster and generalize worse — shrinkage is regularization, and it is why lowering the learning rate always requires raising the number of rounds to compensate.

What XGBoost adds to this skeleton. It takes a second-order view. Write the loss to second order around the current prediction with gradients and Hessians , add an explicit penalty for leaves with weights , and the optimal weight for leaf falls out in closed form:

That gain expression is the split criterion — it replaces Gini/entropy entirely. The term means a split with positive but small gain is rejected outright, which is pre-pruning built into the objective rather than bolted on.

Good and bad.

Good: any differentiable loss plugs straight in; second-order variants give closed-form leaf values and split gains; shrinkage is a clean, single-knob regularizer.

Bad: sequential, so it cannot parallelize across rounds; the learning rate and round count trade off against each other so you must early-stop; it will happily fit label noise given enough rounds.

Follow-up: Learning rate 0.01 with 5000 trees, or 0.1 with 500? → Roughly the same fit, but the slower rate usually generalizes a little better and costs 10x the training and inference time. Pick based on your latency budget; tune rounds by early stopping on a validation set at whichever rate you chose.

Why the interviewer asks this. “Fits the residuals” is the memorized answer. “Gradient descent in function space, and residuals are just what the gradient happens to equal under squared error” is the understood answer, and it immediately predicts how to handle any other loss.

Saying it out loud. “Gradient boosting is gradient descent where the parameters are the predictions themselves. You start with a constant, compute the negative gradient of the loss at every training point, fit a small regression tree to those gradients, and add it in with a small learning rate. For squared error the gradient is literally the residual, which is why people say it fits residuals — but for log loss it’s y minus p, and the general framing is what lets you plug in any differentiable loss. XGBoost extends it to second order, so the leaf values and the split gain both come out in closed form from the gradients and Hessians.”


Q123: XGBoost vs LightGBM vs CatBoost — when does the choice actually matter?

In 30 seconds. “Tuned, all three land within noise of each other on accuracy, so pick on other grounds. LightGBM grows leaf-wise and is fastest on big wide data but overfits small data. CatBoost’s ordered target statistics make it the right call when categorical cardinality is high. XGBoost is the safest, best-documented default. The real gains are in features, not in which of the three you pick.”

Core differences.

XGBoostLightGBMCatBoost
Growthlevel-wise (default)leaf-wiseoblivious / symmetric
Speed on large wide datagoodfastestmoderate
Categoricalsnative (hist mode), or encode yourselfnative, category-set splitsordered target statistics, best in class
Small-data overfittingmost forgivingleast forgivingforgiving
Inference latencygoodgoodfastest (oblivious trees)
Default-out-of-the-box qualityneeds tuningneeds tuningstrongest

Start with the honest headline: on most tabular problems, all three land within noise of each other once tuned, and the differences that matter in practice are training speed, categorical handling, and small-data robustness — not accuracy. An interviewer who has shipped models will respect that framing more than a claim that one is uniformly best.

Why it works.

The real differences are in three mechanisms.

Tree growth policy. XGBoost grows level-wise by default: it expands every node at depth before touching depth . This produces balanced trees and makes max_depth a meaningful, well-behaved regularizer. LightGBM grows leaf-wise: it always splits whichever leaf in the whole tree offers the largest loss reduction. For a fixed number of leaves, leaf-wise reaches lower training loss, because it spends its budget where the error is. It also overfits far more readily on small datasets, since it will happily drive a single branch very deep to isolate a handful of rows. The control knob is therefore num_leaves plus min_data_in_leaf, not max_depth. The classic LightGBM failure is leaving num_leaves at its default of 31 with only a few thousand training rows and wondering why validation loss diverges. XGBoost can be switched to leaf-wise with grow_policy='lossguide'.

Split-finding. LightGBM’s speed comes from two tricks. GOSS (gradient-based one-side sampling) keeps all rows with large gradients — the ones the model is currently wrong about — and randomly subsamples the small-gradient rows, reweighting them to keep the gain estimate unbiased. EFB (exclusive feature bundling) packs mutually-exclusive sparse features, such as the columns of a one-hot encoding, into a single dense feature, cutting effective dimensionality. Combined with histogram binning of continuous features (XGBoost has this too, via tree_method='hist', which is now its default), LightGBM is typically the fastest of the three on wide, large datasets.

Categorical features. This is the axis where the choice genuinely matters. XGBoost historically required you to encode categoricals yourself (one-hot for low cardinality, target encoding for high); it now has native support via enable_categorical=True with tree_method='hist'. LightGBM accepts a categorical feature list and partitions category sets directly. CatBoost is built around the problem: it uses ordered target statistics, which encode a category by the mean target of previous rows only under a random permutation, never including the current row. That last detail is the point — naive target encoding leaks the row’s own label into its own feature and produces validation scores that collapse in production. CatBoost’s ordering scheme is a principled fix, and it also builds combinations of categorical features automatically. If your data is a handful of numeric columns and a pile of high-cardinality categoricals — user IDs, merchant IDs, zip codes — CatBoost is the default worth trying first, and it typically needs the least tuning to get a good first number.

CatBoost’s other distinctive choice is oblivious trees: every node at a given depth uses the same split condition, so the tree is a full binary decision table. This is a strong regularizer and makes inference extremely fast, because scoring is an index computation rather than a branchy traversal.

Good and bad.

XGBoost. Good: most battle-tested, widest deployment tooling, best documentation, most forgiving on small data. Bad: needs tuning to reach its best; historically weakest categorical story.

LightGBM. Good: fastest training on large wide datasets thanks to GOSS and EFB; native category-set splits. Bad: leaf-wise growth overfits small data unless you pull num_leaves down and raise min_data_in_leaf.

CatBoost. Good: best-in-class categorical handling with no target leakage; strongest out-of-the-box quality; fastest inference from oblivious trees. Bad: slower to train than LightGBM on wide numeric data; the symmetric-tree constraint can cost accuracy when the true structure is asymmetric.

Practical decision rule: LightGBM when training time on a large dataset is the bottleneck; CatBoost when categorical cardinality is high or you want a strong baseline with minimal tuning; XGBoost when you want the most battle-tested, best-documented option with the widest deployment tooling and you have the budget to tune it. Do not spend a week choosing — spend it on features, which will move the metric more.

Follow-up: Which hyperparameters actually matter? → In rough order: learning rate paired with number of rounds (set by early stopping), tree complexity (max_depth for XGBoost/CatBoost, num_leaves and min_data_in_leaf for LightGBM), then row and column subsampling. The regularization terms lambda, alpha, and gamma are worth a coarse sweep and rarely the difference between a good and a bad model.

Why the interviewer asks this. They are checking for shipping experience. The tell is whether you name mechanisms — leaf-wise growth, ordered target statistics — or just recite “LightGBM is faster.”

Saying it out loud. “Honestly, tuned, they’re usually within noise of each other on accuracy, so I pick on other grounds. LightGBM grows leaf-wise and uses gradient-based sampling, so it’s the fastest on big wide data — but it overfits small data unless you pull num_leaves down. CatBoost uses ordered target statistics for categoricals, which avoids the target leakage you get from naive target encoding, so it’s my first pick when there’s a lot of high-cardinality categorical data. XGBoost is the safest, most documented default. The real gains are in features, not in which of the three I pick.”


Q124: Explain PCA and derive it. Why SVD rather than eigendecomposition of the covariance matrix?

In 30 seconds. “PCA finds orthogonal directions of maximum variance. Center the data, maximize w-transpose-C-w subject to unit norm, and the Lagrangian immediately gives C-w equals lambda-w — the components are eigenvectors of the covariance and the variance explained is the eigenvalue. You use SVD instead of eigendecomposing the covariance because forming X-transpose-X squares the condition number, destroying precision on exactly the small eigenvalues you care about.”

The short version.

  • Center (and usually standardize) the data first — this is not optional.
  • Components are the eigenvectors of the covariance , ordered by eigenvalue.
  • The eigenvalue is the variance explained by that component.
  • The right singular vectors of the centered are those eigenvectors, with .
  • Use SVD: better conditioning, cheaper when , and truncatable.

Why it works.

Principal component analysis finds an orthogonal set of directions in feature space, ordered so that the first captures the most variance in the data, the second captures the most variance among directions orthogonal to the first, and so on. Projecting onto the top directions gives the best rank- linear approximation of the data in the least-squares sense.

The math, and what it buys you.

Derivation. Let be the data matrix with the column means already subtracted — centering is not optional, and skipping it makes the first component point at the mean rather than at the direction of variation. The sample covariance is .

We want the unit vector maximizing the variance of the projection :

Form the Lagrangian and set the gradient to zero:

So the stationary points are exactly the eigenvectors of , and at such a point the objective value is . The maximum is therefore the eigenvector with the largest eigenvalue, and the variance it explains is that eigenvalue. Repeating the argument under the added constraint of orthogonality to the components already found gives the rest, in descending eigenvalue order. Because is real symmetric and positive semi-definite, the eigenvalues are real and non-negative and the eigenvectors can be chosen orthonormal — so the components form a genuine orthonormal basis.

The SVD connection. The singular value decomposition writes with and orthonormal and diagonal with non-negative entries. Substitute:

That is an eigendecomposition of . So the right singular vectors are the principal components, and the eigenvalues are . Verified on 500 samples of 6 correlated features:

eig vals : [2.6746298e+01 1.4516113e+01 8.4506620e+00 2.4447220e+00 1.2687790e+00 2.6360e-03]
S^2/(n-1): [2.6746298e+01 1.4516113e+01 8.4506620e+00 2.4447220e+00 1.2687790e+00 2.6360e-03]
max |diff|: 1.24e-14
|cos| between components: [1. 1. 1. 1. 1. 1.]

Identical to machine precision, and each component matches up to sign (eigenvector sign is arbitrary — a fact that trips people up when comparing PCA runs).

Good and bad.

Why SVD is the right implementation. Three reasons, and the first is the one that earns the point.

Numerical conditioning. Forming squares the condition number: . Every digit of precision you had in , you lose two of in . On a small ill-conditioned example the measured numbers are and — a matrix that is merely awkward becomes numerically singular in double precision. SVD operates on directly and never forms the product, so it keeps the better conditioning. Small eigenvalues, which are exactly the ones that tell you the intrinsic dimensionality, are the first casualties of the squaring.

Cost when . If you have 200 samples of 20,000 genes, is — 3.2 GB in float64 and expensive to decompose — while the data matrix is tiny. SVD costs and never materializes the big covariance.

Truncation. Randomized and truncated SVD compute only the top singular triplets in roughly , which is what you actually want when out of .

Two practical notes on PCA’s limitations. Standardize (not just center) when features have different units, because PCA maximizes raw variance and a feature measured in millimetres will dominate the same feature measured in metres. And PCA is unsupervised — it optimizes variance, not label separability — so the discarded low-variance direction can be the only one carrying the signal. When the goal is class separation, LDA optimizes the right thing.

Follow-up: How do you pick ? → Cumulative explained variance ratio against a threshold like 95%, the elbow of the scree plot, or — best when PCA feeds a supervised model — cross-validated downstream performance, treating as an ordinary hyperparameter.

Why the interviewer asks this. The Lagrangian derivation shows you can do constrained optimization; the SVD-versus-covariance question separates people who have implemented PCA from people who have called .fit().

Saying it out loud. “PCA finds orthogonal directions of maximum variance. You center the data, maximize w-transpose-C-w subject to w having unit norm, and the Lagrangian immediately gives you C-w equals lambda-w — so the components are eigenvectors of the covariance and the variance explained is the eigenvalue. In practice you use SVD on the centered data instead of eigendecomposing the covariance, because forming X-transpose-X squares the condition number, so you lose precision exactly on the small eigenvalues you care about. It’s also much cheaper when you have way more features than samples, since you never build the d-by-d covariance at all.”


Q125: When do tree ensembles still beat deep learning?

In 30 seconds. “On tabular data, boosted trees usually still win, and it’s about inductive bias. Neural nets have a smoothness prior and are rotationally invariant, but tabular columns have individual meaning and the targets often have hard thresholds — a tree gets a step function with one split, an MLP has to approximate it. I’d reach for deep learning when a column has actual unstructured content.”

The short version.

Trees win whenDeep learning wins when
Heterogeneous, non-smooth columns with hard thresholdsFree text, images, or event sequences in a column
Thousands to hundreds of thousands of rowsData large enough to learn representations
Mixed types, missing values, no scaling pipelineMulti-task or transfer learning across targets
CPU-minute training, sub-ms CPU inferenceStrong relational structure (graph networks)
Regulators want stable SHAP valuesHybrid: neural encoder → embeddings → GBM

Why it works.

Heterogeneous, non-smooth features. Neural networks have a smoothness prior — they build predictions from compositions of smooth functions, and they are biased toward solutions that vary gently over the input space. Tabular targets are often genuinely non-smooth: risk jumps at a credit-score threshold, price jumps at a category boundary. Trees represent a step function natively with one split; an MLP has to spend capacity approximating that step and will round its corners. This is the central argument in the 2022 Grinsztajn, Oyallon and Varoquaux benchmark study “Why do tree-based models still outperform deep learning on tabular data?”, which also isolates two other causes: neural nets are hurt much more by uninformative features, and MLPs are rotationally invariant while real tabular data is not — the columns have individual meaning, and a model that treats an arbitrary rotation of the features as equivalent is throwing away that structure. Trees are the opposite: axis-aligned by construction.

Scale of data. Deep learning’s advantage comes from learning representations, which requires enough data to learn them. With a few thousand to a few hundred thousand rows — the size of most business datasets — boosted trees win comfortably. Neural nets need the sample counts that only images, text, and audio naturally provide.

Mixed types and missing values. LightGBM and XGBoost route missing values down a learned default branch, treating missingness as information, with no imputation step. Trees are invariant to any monotone transform of a feature, so no scaling, no log transforms, no outlier clipping. A neural net needs all of that pipeline, and each stage is a chance to introduce leakage or a train/serve skew bug.

Good and bad.

Operational reasons that matter more than people admit. Boosted trees train in minutes on CPU, so you can iterate on features many times a day. They give you stable, cheap feature importances and SHAP values that regulators and product managers accept. Inference is sub-millisecond on CPU with no accelerator. And there is no learning-rate schedule, no warmup, no batch-size interaction, no divergence at 3 a.m.

Where deep learning does win on tabular-adjacent problems. When there is genuine unstructured content in a column — free-text descriptions, images, sequences of events — an embedding model beats any feature you can hand-craft. When you need multi-task or transfer learning across related targets. When the data has strong relational structure that a graph network can exploit. And in the increasingly common hybrid: use a neural encoder to embed the text and categorical fields, then feed those embeddings, plus the raw numeric columns, into a gradient-boosted model. That hybrid is usually the right architecture when you have both kinds of signal.

It is worth flagging that this is an active area — transformer-style tabular architectures (FT-Transformer, TabPFN and its successors) have narrowed the gap and TabPFN-class models are genuinely strong on very small datasets, where a single forward pass of a pretrained model beats a fitted GBM. Time-sensitive claim: the exact frontier of “deep learning has caught up on tabular data” moves every year; the durable part of the answer is why trees have the better inductive bias for heterogeneous columns, not a leaderboard position.

Follow-up: Would you ever ensemble the two? → Yes, and it usually helps a little, because their errors are decorrelated — the neural net is smooth where the tree is piecewise-constant. Blend with weights fit on a held-out set. Whether the gain justifies maintaining two training pipelines is a separate, usually negative, judgment.

Why the interviewer asks this. They want to see technical judgment rather than fashion-following, and specifically whether you can articulate inductive bias as the reason rather than saying “trees just work better on tables.”

Saying it out loud. “On tabular data, boosted trees usually still win, and it’s about inductive bias. Neural nets have a smoothness prior and they’re rotationally invariant, but tabular columns have individual meaning and the targets often have hard thresholds. A tree gets a step function with one split; an MLP has to approximate it. Trees also handle missing values and mixed scales natively, train in minutes on CPU, and give you SHAP values people trust. I’d reach for deep learning when there’s actual unstructured content — text or images in a column — and often the best answer is a hybrid: embed the text with a neural model, then feed those embeddings into the GBM alongside the numeric columns.”


Q126: What is the curse of dimensionality, concretely?

In 30 seconds. “In a hundred-dimensional unit cube, 99.99999998% of the volume is within 0.1 of a face — there is no interior, so every prediction is an extrapolation. And drop a thousand random points in a thousand dimensions and the farthest is only about 12% farther than the nearest, so ‘nearest neighbour’ stops meaning anything. The reason ML works at all is that real data sits on a much lower-dimensional manifold.”

The short version.

  • Volume concentrates in the shell — high-dimensional data has no middle.
  • Distances stop discriminating — max/min distance ratio converges to 1.
  • Sample requirements explode — cells for a resolution- grid.
  • The inscribed ball vanishes — all the volume is in the corners.
  • The escape hatch: the manifold hypothesis. The curse is about ambient dimension.

Why it works.

The curse of dimensionality is the collection of ways that geometric intuition built in two or three dimensions becomes actively wrong in high dimensions. The abstract statement — “data becomes sparse” — is not convincing on its own. Numbers are.

The math, and what it buys you.

1. Volume concentrates in the shell. Take the unit hypercube and ask what fraction of its volume lies within of some face. The interior cube has volume , so the shell fraction is :

d=  1  interior=8.000e-01  shell=20.0%
d=  2  interior=6.400e-01  shell=36.0%
d=  3  interior=5.120e-01  shell=48.8%
d= 10  interior=1.074e-01  shell=89.3%
d= 50  interior=1.427e-05  shell=99.9986%
d=100  interior=2.037e-10  shell=99.99999998%

In 100 dimensions essentially every point is near a boundary. There is no “middle” of a high-dimensional dataset. Every prediction is an extrapolation in some coordinate.

2. Distances stop discriminating. Draw 1000 uniform points in and a query point, and look at the spread between nearest and farthest neighbour relative to the nearest:

d=    1  min=0.000  max=0.986  (max-min)/min = 2574.66
d=    2  min=0.017  max=1.128  (max-min)/min =   66.06
d=   10  min=0.453  max=1.942  (max-min)/min =    3.28
d=  100  min=3.343  max=4.786  (max-min)/min =    0.43
d= 1000  min=12.128 max=13.548 (max-min)/min =    0.12

At , the farthest of a thousand points is only 12% farther away than the nearest. This is the formal result of Beyer et al. (1999): under broad conditions the ratio of max to min distance converges to 1. Every method whose core operation is “find the closest thing” — -NN, kernel methods with an RBF kernel, DBSCAN, cosine retrieval over raw high-dimensional features — degrades toward meaninglessness, because “closest” stops being a distinguished status.

3. Sample requirements explode. To cover with a grid of resolution per axis you need cells: 10 in one dimension, 100,000 in five, and in ten. To hold local density constant while adding a dimension, you multiply your dataset by 10. This is why non-parametric methods — which need enough neighbours within a small radius to estimate a local average — have convergence rates that degrade as and become useless past a modest .

4. The inscribed ball vanishes. The ball inscribed in the unit cube, touching every face, occupies:

d=  2 : 7.854e-01 of the cube
d=  5 : 1.645e-01
d= 10 : 2.490e-03
d= 20 : 2.461e-08
d= 50 : 1.537e-28

In 50 dimensions the inscribed ball is of the cube it fits inside. All the volume is in the corners — of which there are . This is why a Gaussian in high dimensions does not concentrate at its mode: its mass lives in a thin annulus at radius , so the most likely single point is one almost no sample ever lands near.

Good and bad.

Why anything works at all. The redeeming fact is the manifold hypothesis: real data of nominal dimension typically lies on or near a manifold of much lower intrinsic dimension. A image lives in , but natural images occupy a vanishingly thin sliver of that space. The curse applies to the ambient dimension; learning algorithms succeed by discovering the intrinsic one. That is exactly what PCA, autoencoders, and the hidden layers of any deep network are doing.

Practical consequences to state. Prefer models with strong structural priors in high dimensions — linear models with , or trees, which only ever look at one axis at a time. Distance-based methods need dimensionality reduction first. Regularization stops being optional. And be suspicious of an RBF-kernel SVM or a -NN baseline on raw 1000-dimensional features; it is probably measuring noise.

Follow-up: Why do embeddings work if they are 768- or 1536-dimensional? → Because they are learned to place semantically similar items close together, so the data occupies a low-dimensional structure inside that space rather than filling it uniformly. The curse is a statement about uniformly-filled space. Cosine similarity over trained embeddings works; cosine similarity over 768 random features does not.

Why the interviewer asks this. Everyone can say “data gets sparse.” Having the shell fraction, the distance-ratio collapse, and the sampling number ready shows you have actually internalized the geometry, and it is precisely the reasoning you need to debug a nearest-neighbour system that quietly stopped working.

Saying it out loud. “The number that makes it real for me: in a hundred-dimensional unit cube, 99.99999998% of the volume is within 0.1 of a face. There’s no interior. And if you drop a thousand random points in a thousand dimensions, the farthest one is only about 12% farther than the nearest — so ‘nearest neighbour’ stops meaning anything, and everything distance-based falls over. The reason ML works at all is the manifold hypothesis: real data sits on a much lower-dimensional surface inside that huge space, and the whole job of representation learning is finding it.”


Evaluation and Data Discipline

Q127: Walk me through precision, recall, F1, ROC-AUC and PR-AUC — and when each is the right choice.

In 30 seconds. “Precision is ‘of what I flagged, how much was real’; recall is ‘of what’s real, how much did I catch.’ Which one matters is a cost question, not a statistical one. ROC-AUC is the probability a random positive outranks a random negative — threshold-free and prevalence-invariant. PR-AUC is the one to trust when positives are rare, because its baseline is the base rate.”

The short version.

MetricQuestion it answersThreshold-free?Prevalence-invariant?Use when
PrecisionOf what I flagged, what was real?no — fixed thresholdnoFalse positives are costly
RecallOf what’s real, what did I catch?no — fixed thresholdnoFalse negatives are costly
F1Harmonic balance of the twononoYou need one number and both sides matter
ROC-AUCDoes a random positive outrank a random negative?yesyesBalanced classes; comparing across base rates
PR-AUCHow does precision hold up across recall?yesnoPositives are rare; you only care about the positive class
AccuracyOverall hit ratenonoAlmost never on imbalanced data

Why it works.

Everything starts from the confusion matrix. For a binary classifier at a fixed decision threshold, TP is a positive correctly called positive, FP a negative wrongly called positive, FN a positive missed, TN a negative correctly rejected.

Precision answers “of the things I flagged, what fraction were real?” — it is the quality of your alerts, and its denominator is what you predicted. Recall (also sensitivity, or true positive rate) answers “of the real things, what fraction did I catch?” — its denominator is the ground truth. They trade off through the threshold: lower it and recall rises while precision falls.

The choice between them is a business question, not a statistical one, and the way to answer it in an interview is to name the asymmetric cost. If a false negative means a missed cancer diagnosis and a false positive means one extra biopsy, you optimize recall. If a false positive means wrongly blocking a paying customer’s transaction and a false negative means absorbing the fraud loss, you weigh precision against the actual dollar amounts. Say the costs out loud — that is the signal the interviewer is listening for.

ROC-AUC sweeps the threshold and plots TPR against FPR. Its value has a clean probabilistic meaning: it is the probability that a randomly chosen positive is scored above a randomly chosen negative. So it measures ranking quality, is threshold-independent, and is invariant to prevalence — the same model scored on a 50/50 sample and a 1-in-1000 sample gets the same ROC-AUC. Random guessing is 0.5.

PR-AUC (average precision) sweeps the same thresholds and plots precision against recall. It is not prevalence-invariant: its baseline for a random model is the positive class rate.

The math, and what it buys you.

F1 is the harmonic mean, . The harmonic mean, not the arithmetic mean, because it punishes imbalance: precision and recall gives arithmetic mean but F1 exactly . Use when you want to weight recall times as much as precision — favours recall, favours precision. F1’s real weakness is that it hides which side you are failing on and it ignores true negatives entirely, so always show the underlying precision and recall too.

On a dataset with 0.48% positives, measured:

positives: 961   prevalence: 0.004805
sep=1.5: ROC-AUC=0.8635  PR-AUC=0.0578   baseline PR-AUC=0.0048
sep=2.5: ROC-AUC=0.9611  PR-AUC=0.3544   baseline PR-AUC=0.0048

The second model is genuinely 12x better than random on PR-AUC and looks nearly perfect on ROC-AUC. The PR number is the one that tells you what the alert queue will feel like.

Good and bad.

Decision rule, stated compactly. Use ROC-AUC when the classes are roughly balanced and you care about overall ranking, or when you need a metric that is comparable across populations with different base rates. Use PR-AUC when positives are rare and you only care about performance on the positive class. Use precision/recall at a specific operating point whenever the system has a fixed capacity — “we can review 500 alerts a day, so report precision@500” is almost always the metric the business actually has.

One warning worth volunteering: accuracy on imbalanced data is worthless. With 1,000 positives among 1,001,000 rows, always predicting negative scores accuracy — 99.9% — while catching nothing.

Follow-up: How do you choose the threshold? → Not by leaving it at 0.5. Pick it on a validation set by maximizing expected utility with your real cost matrix, or by pinning the constraint the business actually has — a precision floor, a recall floor, or an alert-volume cap. Then monitor it, since the right threshold drifts as prevalence changes.

Why the interviewer asks this. This is the metric-literacy screen. The candidates who pass do not just define the terms — they connect the choice to a cost asymmetry and mention the operating point.

Saying it out loud. “Precision is ‘of what I flagged, how much was real’; recall is ‘of what’s real, how much did I catch.’ Which one matters is a cost question — missing a cancer versus one extra biopsy pushes you to recall; blocking good customers pushes you to precision. F1’s the harmonic mean so it punishes being lopsided. ROC-AUC is the probability a random positive outranks a random negative, and it’s threshold-free and prevalence-invariant. PR-AUC is the one I trust when positives are rare, because its baseline is the base rate. And in production I usually report precision at whatever alert volume the review team can actually handle.”


Q128: Why does ROC-AUC mislead on imbalanced data?

In 30 seconds. “It’s the FPR denominator. FPR is false positives over all negatives, and when negatives outnumber positives a thousand to one, that denominator is huge — so a mountain of false positives still looks like a tiny FPR. Precision compares false positives against true positives, both small numbers of the same order, so it stays sensitive. Same model, and the PR curve is the one telling you the truth.”

The short version.

ROC curvePR curve
Denominator that absorbs FP — enormous when negatives dominate — small, same order as TP
Sensitivity to added false positivesanaesthetizedfull dynamic range
Prevalenceinvariant (here a bug)moves with it (here a feature)
Baseline for a random model0.5 alwaysthe prevalence
Right headline for a rare-event detectornoyes

Why it works.

Because of the denominator in the false positive rate. , and when negatives massively outnumber positives, is enormous, so the denominator is essentially the total negative count and is nearly constant. That makes FPR insensitive: an alarming number of false positives produces a tiny, reassuring FPR.

The formal reason: precision has compared against , both of which are small numbers of the same order, so it stays sensitive to changes in . FPR compares against , which is huge, so it is anaesthetized.

The math, and what it buys you.

Real numbers. A million negatives, a thousand positives — a 0.1% prevalence, which is realistic for fraud, ad clicks, or rare disease. Your model flags 10,000 cases and catches 900 of the 1,000 positives:

TP=900  FP=9100  FN=100  TN=990900
precision = 0.0900   recall = 0.9000   FPR = 0.0091   F1 = 0.1636

The ROC curve sees recall at FPR — a point deep in the upper-left corner, the picture of an excellent classifier. The precision-recall curve sees recall at precision . Both describe the same model, and both are correct.

Now translate. Precision means that of every 100 alerts your analysts open, 91 are false alarms. Nine thousand one hundred wasted investigations to find nine hundred real cases. That is the number the operations team lives with, and ROC-AUC never showed it to you.

Concretely, adding 9,100 false positives moved FPR from 0 to 0.0091 — visually indistinguishable from the axis — while it moved precision from 1.0 to 0.09, which is the entire dynamic range of that metric.

There is a second, subtler failure. ROC-AUC is prevalence-invariant, which is sometimes a feature and here is a bug. A model with ROC-AUC has the same ROC-AUC whether you evaluate it at 50% prevalence or 0.1%, but its precision at a fixed recall changes by orders of magnitude between those two worlds. If you validated on a rebalanced sample and deployed to the real base rate, ROC-AUC will report no problem at all while precision collapses. PR-AUC, because it moves with prevalence, would have warned you.

Good and bad.

What to do instead. Report PR-AUC (average precision) as the headline for rare-positive problems, always alongside the baseline, which equals the prevalence — quoting “PR-AUC 0.35” without saying the baseline is 0.005 is meaningless. Report precision and recall at the actual operating point. And if you have a capacity constraint, report precision@k for the k you can actually process.

A fair caveat, since a good interviewer may push here: ROC-AUC is not wrong, it answers a different question. If your use case genuinely is “rank these and I will consume the whole ranking,” or you need to compare a model across sites with different base rates, ROC-AUC is the appropriate summary. The mistake is using it as the sole headline number for a rare-event detector.

Follow-up: Is PR-AUC comparable across datasets? → No, and that is the price of its sensitivity. Because the baseline is the prevalence, a PR-AUC of 0.30 at 1% prevalence is a far stronger model than 0.30 at 20%. Always report the baseline, or report the lift over it.

Why the interviewer asks this. It is the fastest way to find out whether someone has actually deployed a rare-event model, because everyone who has, has been burned by exactly this.

Saying it out loud. “It’s the FPR denominator. FPR is false positives over all negatives, and when negatives outnumber positives a thousand to one, that denominator is huge, so a mountain of false positives still looks like a tiny FPR. Concrete case: a million negatives, a thousand positives, you catch 900 with 9,100 false alarms. FPR is 0.009 — beautiful ROC curve. But precision is 0.09, so 91 out of every 100 alerts your analysts open are junk. Same model, and the PR curve is the one that told you the truth.”


Q129: What is calibration, and why doesn’t accuracy imply it?

In 30 seconds. “Calibration means when the model says 0.7, it’s right about 70% of the time. It’s completely separate from accuracy — cube every probability and the ranking is identical, so AUC doesn’t move at all, but the numbers are now badly wrong. It matters any time the probability feeds a decision, because expected-value thresholds are garbage if the probability is garbage, however good the ranking is.”

The short version.

DiscriminationCalibration
What it asksDo positives rank above negatives?Do the numbers mean what they say?
Measured byaccuracy, ROC-AUC, F1reliability diagram, ECE, Brier, log loss
Survives a monotone transform of ?yes, unchangedno, destroyed
Fixed bya better modela post-hoc mapping on held-out data
Matters whenyou only need a sort keythe probability feeds a decision

A model is calibrated when its predicted probabilities match observed frequencies: among all the cases where it says 0.7, about 70% should actually be positive. Formally, for all .

Why it works.

Calibration and discrimination are orthogonal properties, and this is the crux. Discrimination is whether the model ranks positives above negatives — that is what accuracy, ROC-AUC, and F1 measure. Calibration is whether the numbers mean anything as probabilities. A model can be perfect at one and terrible at the other.

Why you should care. Any time a probability is an input to a downstream decision rather than just a sort key, calibration is the thing that matters. Expected-value calculations — “block if ” — are wrong if is wrong, no matter how good the ranking is. Risk aggregation across a portfolio needs probabilities that sum correctly. Thresholds set on one population transfer to another only if the numbers are meaningful. And any human consuming “83% likely” is entitled to have that mean something.

The math, and what it buys you.

The clean demonstration: apply any strictly increasing transform to the predicted probabilities. The ranking is untouched, so every ranking metric is identical, but the numbers are now wrong. Measured on 100,000 samples where the true probability was known:

calibrated        : AUC=0.8342  Brier=0.1662
p^3 (same ranking): AUC=0.8342  Brier=0.2439
sqrt(p)           : AUC=0.8342  Brier=0.1990

ROC-AUC is identical to four decimals across all three because monotone transforms cannot change a ranking. The Brier score — mean squared error of the probabilities — degrades by 47% for the cubed version. Its reliability table:

bin [0.0,0.1)  n=46444  mean_pred=0.025  actual=0.232
bin [0.1,0.2)  n=12053  mean_pred=0.146  actual=0.532
bin [0.4,0.5)  n= 5809  mean_pred=0.449  actual=0.768
bin [0.9,1.0)  n= 3425  mean_pred=0.950  actual=0.984

The model says 0.146 and the event happens 53% of the time. It is systematically, massively underconfident — and every accuracy-style metric would tell you the model is fine.

How to measure it. The reliability diagram is the primary tool: bin predictions, plot mean predicted probability against observed frequency per bin, and compare to the diagonal. Expected Calibration Error is the weighted average absolute gap, ; it is a useful scalar but sensitive to binning choices, so quote the diagram too. The Brier score and log loss are proper scoring rules, meaning they are uniquely minimized by reporting your true beliefs — they capture calibration and discrimination together, which is why “Brier got worse but AUC didn’t move” is a clean calibration diagnosis.

Good and bad.

How to fix it. Fit a post-hoc mapping on a held-out calibration set — never the training set, or you will just relearn the training fit. Platt scaling fits a one-dimensional logistic regression on the model’s scores; it is parametric, works with a few hundred points, and assumes a sigmoid-shaped distortion. Isotonic regression fits any monotone step function; it is more flexible, needs thousands of points, and can overfit on small sets. For neural networks, temperature scaling — divide the logits by a single learned scalar before the softmax — is the standard, because it fixes miscalibration with exactly one parameter and provably cannot change the argmax, so accuracy is untouched.

Who is miscalibrated, and how. Modern deep networks are systematically overconfident, a finding from Guo et al. (2017); the cause is that they are trained to near-zero loss on the training set and keep pushing logits apart after the errors are gone. Naive Bayes is overconfident because its independence assumption multiplies correlated evidence as if it were independent. Boosted trees are typically overconfident at the extremes. Random forests are usually underconfident near 0 and 1, because averaging many trees rarely produces a unanimous vote. Logistic regression trained with log loss on well-specified features is close to calibrated by construction, since log loss is a proper scoring rule and the model is directly optimizing it.

Follow-up: Does class-rebalancing affect calibration? → It destroys it. Oversampling to 50/50 shifts the model’s implicit prior, so predicted probabilities come out roughly at the rebalanced rate rather than the true one. See Q132 for the measured effect and the prior-correction formula.

Why the interviewer asks this. It separates people who treat model outputs as scores from people who treat them as probabilities, and the latter is what you need to build any system that makes a decision with expected value.

Saying it out loud. “Calibration means when the model says 0.7, it’s right about 70% of the time. It’s completely separate from accuracy — if I cube every probability, the ranking is identical, so AUC doesn’t move at all, but the numbers are now badly wrong. I measure it with a reliability diagram and Brier score, and fix it post-hoc on a held-out set with Platt scaling or isotonic, or temperature scaling for a neural net since that’s one parameter and can’t change the argmax. It matters any time the probability feeds a decision — expected-value thresholds are garbage if the probability is garbage, however good the ranking is.”


Q130: Name every form of data leakage you can think of, and how you would detect each.

In 30 seconds. “Leakage is anything in training that wouldn’t be available at prediction time. The big families are target leakage — a column only populated after the outcome — preprocessing fitted before the split, temporal leakage from random-splitting time series, and group leakage where the same user appears on both sides. My standing rule: a suspiciously good score is a bug report, not a result.”

The short version.

#FamilyFastest detection
1Target leakage (post-outcome column)As-of-time audit per column; implausible single-feature importance
2Preprocessing fitted before the splitPut it all in a Pipeline; score drops = you had leakage
3Temporal leakageTime-split and compare to random-split; large gap is diagnostic
4Group leakage (same entity both sides)Intersect entity IDs across folds — should be zero
5Duplicates and near-duplicatesHash dedup, then MinHash / embedding similarity across the split
6Target-encoding leakageUse out-of-fold or ordered encoding and compare
7Label-collection leakageInterrogate how labels were produced, not just what they are
8Row-order / index artifactCorrelate the index with the target
9Hyperparameter / selection leakageUndetectable after the fact — prevent with nested CV
10External joined table built with hindsightTrace provenance and computation date of every join

Why it works.

Data leakage is any situation where information unavailable at prediction time influences training. Its signature is a validation score that is too good and a production score that is much worse. It is the single most common cause of a model that works in the notebook and fails on deployment, and interviewers ask it because catching leakage is most of what separates a careful practitioner from a careless one.

1. Target leakage — a feature that encodes the answer. A discount_applied_after_refund column in a churn model, a days_in_ICU column in a mortality model, an account_closed_date in a default model. The feature exists in the historical table but is only populated after the outcome. Detection: any single feature with implausibly high importance or high univariate AUC deserves an audit; the real test is to ask, for each column, “at the moment I need this prediction, does this value exist yet?” Build a feature dictionary with an as-of timestamp per column and enforce it.

2. Train-test contamination through preprocessing. Fitting a scaler, imputer, PCA, target encoder, or feature selector on the full dataset before splitting. The test set’s statistics have then influenced the transformation. Detection: the fix and the test are the same — put every transformation inside a sklearn.Pipeline and cross-validate the pipeline, not the model. If your score drops when you do that, you had leakage.

3. Temporal leakage. Random splitting of time-ordered data, so the model trains on the future and predicts the past. Also, using a feature computed over a window that extends past the prediction time — a 30-day rolling average centered on today. Detection: split by time and compare to the random-split score; a large gap is diagnostic. Check that every aggregation window is strictly backward-looking.

4. Group leakage. The same entity appearing in both train and test: the same patient with multiple visits, the same user with multiple sessions, the same document in multiple chunks. The model memorizes the entity rather than learning the pattern. Detection: count the intersection of entity IDs between splits — it should be zero. Use GroupKFold.

5. Duplicate and near-duplicate rows. Exact duplicates split across train and test are trivially memorized. Near-duplicates — the same article reposted, augmented copies of an image — are worse because they evade an exact-match check. Detection: hash-based dedup for exact, then MinHash / SimHash or embedding cosine similarity above a threshold for near-duplicates, run across the split boundary.

6. Leakage through the target encoding of a categorical. Computing the mean target per category on the full training set means each row’s own label contributes to its own feature. High-cardinality categories, where a category has one or two rows, are almost pure label. Detection: the effect is invisible in cross-validation if the encoding was fitted outside the fold. Use out-of-fold or ordered target encoding (Q123) and compare.

7. Label leakage through data collection. The labels were produced by a process correlated with a feature — for example, cases were only labelled positive if a human reviewed them, and reviews were triggered by a rule that uses one of your features. The model relearns the triggering rule. Detection: interrogate how labels were generated, not just what they are. This is an interview question in itself and the answer is always “go ask the person who built the labelling pipeline.”

8. Leakage via row order or an index artifact. IDs assigned sequentially by time or by outcome, so the row index itself predicts the label. Data sorted by class. Detection: check the correlation of the index with the target — a genuinely alarming number of public datasets fail this.

9. Hyperparameter and selection leakage. Tuning hyperparameters or selecting features against the test set, repeatedly. Each glance at the test set leaks a little information, and after fifty experiments the test estimate is optimistic. Detection: structurally impossible to detect after the fact — prevent it with a three-way split, or nested cross-validation, and a genuinely untouched holdout opened once.

10. Leakage through external data joined by key. Joining an enriched table that was itself built with knowledge of the outcome period — a “customer lifetime value” column computed over all of history. Detection: trace the provenance of every joined table to its computation date.

Good and bad.

The universal detection heuristics. First, a suspiciously good score is a bug report, not a result — 0.99 AUC on a hard problem means you have leakage until proven otherwise. Second, ablate: drop the single most important feature and see whether performance collapses to plausible. Third, the as-of-time audit: for every feature, state the timestamp at which its value becomes known, and confirm it precedes the prediction time. Fourth, the ultimate test — build a temporally held-out set from a period after all your development data, and score it once.

Follow-up: You find leakage after the model shipped. What now? → Quantify first: retrain without the leaky feature and measure the honest performance, since the deployed model may still be net-positive. Then decide whether to roll back or to keep it running while the fix is built, based on that honest number against the incumbent. And write the as-of-time check into the feature store so the class of bug cannot recur.

Why the interviewer asks this. Leakage is the highest-frequency real-world ML failure, and the breadth of your list is a direct proxy for how many datasets you have personally been burned by.

Saying it out loud. “Leakage is anything in training that wouldn’t be available at prediction time. The big families are target leakage — a column that’s only populated after the outcome — preprocessing fitted before the split, temporal leakage from random-splitting time series, and group leakage where the same user or patient appears on both sides. My standing rule is that a suspiciously good score is a bug report. And the check I run on every column is: at the moment I need this prediction, does this value exist yet? If I can’t answer that with a timestamp, I don’t trust the feature.”


Q131: How do you set up cross-validation when rows are grouped, or ordered in time?

In 30 seconds. “Plain k-fold assumes rows are exchangeable, and grouped or time-ordered data isn’t. For groups I use GroupKFold and pick the grouping key by asking what the deployment population looks like. For time series it’s forward chaining — train on the past, test on the future — and the detail people miss is the purge: if the label takes 30 days to materialize, leave a 30-day gap before the test block.”

The short version.

StructureSchemeThe detail people miss
i.i.d. rowsplain KFold
Grouped entitiesGroupKFold / StratifiedGroupKFoldPick the key by deployment population, not by convenience
Time-orderedTimeSeriesSplit forward chainingPurge the label horizon; embargo after the test block
Drifting relationshiprolling window instead of expandingIf rolling wins, you have measured concept drift
Grouped and temporaltime-split globally, then hold out entity IDsDon’t group-split inside a time-random split
Spatialblocked spatial CVNearby pixels leak into each other

Why it works.

The assumption behind ordinary -fold cross-validation is that rows are exchangeable — independent and identically distributed, so any partition is as good as any other. Grouped data violates independence; time-ordered data violates both independence and the premise that the future is predictable from a randomly-chosen subset of the past. Applying plain -fold to either produces an optimistic estimate, sometimes wildly so.

Grouped data. If several rows share a latent entity — multiple visits by one patient, multiple sessions by one user, multiple chunks from one document, multiple photos of one product — those rows are correlated. Random splitting puts some of an entity’s rows in train and some in test, and the model can score well by memorizing the entity rather than learning the signal. Use GroupKFold, which guarantees no group is split across folds, or StratifiedGroupKFold when you also need class balance preserved.

The correct grouping key is the one that matches how the model will be used. If it will see brand-new users, group by user. If it will see new sessions from known users, random splitting within a user is actually the honest setup. Ask what the deployment population is, and let that pick the key — this is the part interviewers are testing.

Two practical wrinkles. Group sizes are usually skewed, so folds end up unbalanced in row count; check that no fold is dominated by one whale. And when there are multiple candidate grouping keys — a patient belongs to a hospital, which belongs to a region — pick the coarsest level at which you need generalization; if the model will be deployed to a new hospital, group by hospital, not patient.

The math, and what it buys you.

Time-ordered data. Two rules. Never train on data that comes after your validation data. And respect the gap between when a feature is known and when the label is known.

The standard scheme is forward-chaining (expanding window), TimeSeriesSplit:

fold 1: train [1..100]  test [101..120]
fold 2: train [1..120]  test [121..140]
fold 3: train [1..140]  test [141..160]

Every test block is strictly after its training block, and the training set grows, which mirrors production where you retrain on everything you have. The rolling window variant fixes the training length instead of expanding it — train on [21..120], then [41..140] — which is the right choice when the relationship drifts and old data is actively misleading. Compare the two empirically: if the rolling window wins, you have measured concept drift, which is a useful finding to report on its own.

The gap — the detail that distinguishes a good answer. If your label takes 30 days to materialize (did the customer churn within 30 days?), then at the moment you would have trained the model you did not yet know the labels for the last 30 days of your training window. Training right up to the test boundary is leakage. Insert a purge of at least the label horizon between train and test, and if features use backward-looking windows, add an embargo after the test block too, so training rows just after the test period cannot see into it through their own rolling windows. This is PurgedGroupTimeSeriesSplit in the quantitative finance literature (López de Prado), and it is exactly the right machinery whenever labels have a lag.

Good and bad.

Grouped and temporal at once, which is the common real case — many users, each with events over time. Split by time globally so no fold sees the future, and if you also need generalization to unseen users, additionally hold out a set of user IDs. Do not group-split by user within a time-random split; that fixes one leak and leaves the other.

Other structure to watch for. Spatial autocorrelation needs blocked spatial CV, not random points, or nearby training pixels leak into test pixels. Nested hierarchies need the outer level as the group. And any dataset with a “session” or “batch” column recorded by the collection process usually has a batch effect worth grouping on.

Follow-up: How many folds, and does that change here? → For time series the number of folds is governed by how much data you can afford to withhold from the first training window, not by the usual bias-variance argument; you often end up with 3 to 5. Also note the folds are not exchangeable — later folds have more training data and different market conditions — so quote the per-fold scores, not just the mean, and look at the trend.

Why the interviewer asks this. Nearly every real dataset has group or time structure, and defaulting to KFold is the most common way a candidate produces a number that is quietly meaningless.

Saying it out loud. “Plain k-fold assumes rows are exchangeable, and grouped or time-ordered data isn’t. For groups — same patient, same user, same document — I use GroupKFold, and I pick the grouping key by asking what the deployment population looks like: if we’ll see brand-new users, group by user. For time series it’s forward chaining, train on the past, test on the future, and the detail people miss is the purge — if the label takes 30 days to materialize, you have to leave a 30-day gap before the test block, otherwise you’re training on labels you wouldn’t have had.”


Q132: How do you handle class imbalance, and what does each fix cost you?

In 30 seconds. “First check whether imbalance is actually the problem — what hurts is having few positives in absolute terms, not the ratio. Usually the complaint is ‘it predicts everything negative,’ and that’s just a threshold sitting at 0.5. And the thing everyone forgets: every resampling and reweighting fix silently destroys your probability calibration.”

The short version.

FixWhat it costs you
Change the metric and the thresholdNothing. Do this first. Calibration preserved exactly.
Class weights / cost-sensitive lossCalibration breaks; label noise on the minority is amplified by the weight
Random oversamplingMemorization of duplicated rows; longer training; calibration breaks
Random undersamplingThrows away real data; higher variance — but fast, and good inside an ensemble
SMOTE and variantsInterpolation assumption fails near boundaries and in high dimensions; bad with categoricals
Get more positivesTime and money — and usually the highest return of anything on this list

Why it works.

First, the answer an interviewer most wants to hear: check whether you have a problem at all. Imbalance is not intrinsically harmful. What harms you is too few positive examples in absolute terms and an evaluation metric or loss that ignores the minority class. A hundred thousand positives out of ten million is a 1% rate and a perfectly learnable problem. Two hundred positives out of twenty thousand is the same 1% and a genuinely hard problem — and the difficulty is the two hundred, not the ratio. So diagnose before you treat.

The interventions, and the cost of each.

Do nothing to the data; change the metric and the threshold. Train normally with log loss, evaluate with PR-AUC, and move the decision threshold to the operating point your business wants. Cost: none. This is the correct first move and it solves most cases, because the usual complaint (“the model predicts everything as negative”) is a threshold artifact at 0.5, not a training failure. It preserves calibration exactly.

Class weights / cost-sensitive loss. Weight the minority class up in the loss, e.g. class_weight='balanced' or scale_pos_weight in XGBoost. Cost: it is mathematically a reweighting of the objective, so the model no longer estimates — it estimates a tilted version. Calibration breaks. It also amplifies label noise on the minority class by the same weight, so a mislabelled positive now costs you 100x. Gradient variance rises.

Random oversampling of the minority. Duplicate minority rows. Cost: exact duplicates give the model the opportunity to memorize them, so overfitting risk rises, and training time grows with the dataset. Calibration breaks.

Random undersampling of the majority. Throw away majority rows. Cost: you are discarding real data, which is the most expensive thing you own; variance rises because the effective sample is smaller. Its virtue is speed, and it works well inside an ensemble — train several models on different majority subsamples and average, which recovers the discarded information (EasyEnsemble / BalancedBagging).

SMOTE and variants. Synthesize new minority points by interpolating between a minority point and one of its nearest minority neighbours. Cost: the interpolation assumes the region between two minority points is minority, which is false near a class boundary and false in high dimensions (see Q126 — “between” is not a well-behaved concept there). It performs badly with categorical features (SMOTE-NC patches this crudely) and it can generate points inside the majority region, actively creating label noise. Empirically SMOTE often fails to beat plain class weighting on tabular data, and it is worth saying so rather than reciting it as a best practice.

Get more positives. Targeted labelling, active learning on high-uncertainty cases, or relaxing the positive definition to a related, more frequent proxy event. Cost: time and money — and it is usually the highest-return option by a wide margin.

The math, and what it buys you.

The point that ties them together: every resampling and reweighting method breaks calibration. They all change the effective class prior the model is trained under, so the output probabilities come out near the manipulated rate rather than the true one. Measured, on data with a 1.69% true positive rate:

plain      : AUC=0.8446  Brier=0.015641  mean_pred=0.0172  actual=0.0170
oversampled: AUC=0.8447  Brier=0.165720  mean_pred=0.3328  actual=0.0170
corrected  : AUC=0.8447  Brier=0.015645  mean_pred=0.0174

Read that carefully. Oversampling to 50/50 changed ROC-AUC by — it did not improve discrimination at all — while the Brier score got ten times worse and the mean predicted probability went from a correct 1.72% to 33%. The model now says “one in three” about events that happen one in sixty.

The correction, when you have resampled from a true prior to a training prior :

Applying it recovered Brier against the uncorrected — back to the plain model’s . Equivalently, for a logistic model, just subtract from the logit. Or skip the whole detour and recalibrate on a held-out set with the true class distribution, which is more robust and handles boosted trees too.

Follow-up: What would you actually do first on a 1%-positive fraud problem? → Train unmodified with log loss, evaluate with PR-AUC and precision at the alert volume the review team can handle, and tune only the threshold. If positives are scarce in absolute count, spend effort on getting more labels before touching the sampler. Reach for class weights only if the loss is genuinely being swamped, and recalibrate afterward.

Why the interviewer asks this. SMOTE is the cached answer and it is often the wrong one. What they want is someone who diagnoses first, knows that thresholds fix most of it, and knows that every resampling fix silently costs you probability calibration.

Saying it out loud. “First I check whether imbalance is actually the problem — what hurts is having few positives in absolute terms, not the ratio. Usually the complaint is ‘it predicts everything negative,’ and that’s just a threshold at 0.5, so I move the threshold and evaluate with PR-AUC and I’m done. If I do need more, class weights before SMOTE — SMOTE interpolates between minority points and that assumption falls apart near the boundary and in high dimensions. And the thing everyone forgets: all of these break calibration. I measured it — oversampling to 50/50 left AUC unchanged to four decimals but pushed the mean predicted probability from 1.7% to 33%. If you need real probabilities you have to correct the prior or recalibrate afterward.”


Q133: What makes a good feature, and what is feature engineering doing that a deep model cannot?

In 30 seconds. “A good feature is predictive given what you already have, available at prediction time with the same value it’ll have in production, stable, and cheap enough to serve. What feature engineering does that a deep model can’t is inject information that isn’t in the data at all, and aggregate across rows — the model only ever sees one row.”

The short version.

  • Predictive — carries marginal signal given the features you already have.
  • Available at prediction time — same value it will have in production (the Q130 question).
  • Stable — distribution and relationship to the target don’t drift, and no upstream team silently redefines it.
  • Cheap enough — computable inside the latency budget from data that exists at serving time.

The fourth one is where production systems actually die. A feature that requires a join against a table refreshed nightly cannot serve a real-time request. A feature computed differently in the training SQL than in the serving code is train/serve skew, and it is the reason feature stores exist.

Why it works.

What feature engineering does that a deep model cannot.

It injects information the model does not have. This is the big one and it is not a matter of capacity. If you compute “distance from this transaction to the customer’s usual location,” you have brought in geography and a notion of usual that is nowhere in the raw columns. No amount of depth extracts external knowledge from data that does not contain it. Domain features are a channel for information, not a substitute for capacity.

It supplies the right inductive bias cheaply. A network can in principle learn that the ratio of two columns matters — universal approximation guarantees it in the limit — but it needs enough data to discover the ratio, and division is a hard function for a ReLU stack to represent. Handing it debt/income directly costs one line and saves a large amount of data. Same for cyclic encodings: hour as an integer tells a tree that 23 and 0 are far apart; (sin(2πh/24), cos(2πh/24)) encodes that midnight is adjacent to 11pm. That is a fact about clocks, and you know it and the model does not.

It aggregates across rows the model never sees together. Any model consumes one row at a time. “Number of transactions by this card in the last hour,” “user’s average session length over 30 days,” “count of distinct merchants this week” are cross-row aggregates. Unless you build a sequence or graph model, these are structurally invisible to the architecture and are usually the strongest features in fraud, churn, and recommendation systems.

It regularizes by reducing dimensionality with knowledge. Bucketing a continuous feature at a known clinical or regulatory threshold builds in a step that would otherwise cost data to learn.

Good and bad.

Where feature engineering loses. On unstructured data — pixels, raw audio, text — hand-crafted features (SIFT, HOG, MFCCs, n-gram counts) were comprehensively beaten by learned representations, and that is not coming back. The reason is that the useful features there are hierarchical compositions with no compact human description; a person can write down “debt over income” but cannot write down “the third-level texture detector that fires on fur.” When the useful representation is inexpressible in words and you have enough data, learn it. When the useful representation is a fact you already know, type it in.

Practical process. Start from the domain question — ask a fraud analyst what they look at, and encode that. Then the mechanical families: ratios and differences between related quantities, time deltas since the last event, rolling aggregates at several windows, counts and distinct-counts, deviation from an entity’s own baseline, and interactions between a categorical and a numeric. Validate each with out-of-fold performance and permutation importance, not with training-set gain, which is biased toward high-cardinality features. And check drift on every feature you ship, because a stable feature is worth more than a slightly stronger unstable one.

Follow-up: How do you know whether a new feature is actually helping? → Cross-validated performance with and without it, on the split scheme that matches deployment, and a permutation importance computed out-of-fold. If the gain is within the fold-to-fold standard deviation, it is not real. And check its importance is not concentrated on a handful of rows, which is the fingerprint of leakage.

Why the interviewer asks this. “Deep learning does feature engineering for you” is a common half-truth. The full picture — that engineering injects external information and cross-row structure that no architecture can conjure from a single row — is the mark of someone who has shipped a tabular model.

Saying it out loud. “A good feature is predictive given what you already have, available at prediction time with the same value it’ll have in production, stable, and cheap enough to serve. What feature engineering does that a deep model can’t is inject information that isn’t in the data — a distance from the customer’s usual location brings in geography that no amount of depth would find — and aggregate across rows, since the model only ever sees one row. On images and text, learned representations won and that’s settled. On tabular data, the domain features are still where the wins are.”


Training Fundamentals

Q134: Batch norm vs layer norm — mechanism, and why transformers use layer norm.

In 30 seconds. “Same operation, different axis. BatchNorm normalizes each feature across the batch; LayerNorm normalizes each sample across its features. Everything else follows from that. Transformers use LayerNorm because sequences have different lengths and get padded, so batch statistics would depend on what else happened to be in the batch — LayerNorm only looks at one token’s own hidden vector.”

The short version.

BatchNormLayerNorm
Statistics overeach feature, across the batcheach sample, across the features
Effect on a matrixmakes each column standardmakes each row standard
Train vs testdifferent — needs running averagesidentical, no mode switch
Small batchesdegrades badly (noisy estimates)unaffected
Couples examples?yesno
Distributed trainingneeds SyncBN all-reduce per layerfree
Variable sequence length / paddingstatistics depend on batch compositionexactly invariant
Inference costfolds into the preceding conv — freea real op

Both normalize activations to zero mean and unit variance and then apply a learned scale and shift, . The entire difference is which axis the statistics are computed over, and every downstream consequence follows from that one choice.

Why it works.

Why transformers use layer norm — the sequence-length argument. This is the specific reason and the one to lead with. A transformer batch has shape (batch, sequence length, model dimension), and sequences have different lengths, so they are padded. BatchNorm would compute per-feature statistics over the batch-and-time axes, which means the statistics depend on how much padding is in the batch and on how long the other sequences happen to be. Two identical sentences batched with different neighbours would normalize differently. That is unacceptable.

LayerNorm normalizes over the model dimension only, so each token’s normalization depends on that token’s own activations and nothing else. It is exactly invariant to batch composition, sequence length, and padding. It also works identically during autoregressive generation, where the effective batch is one token at a time and BatchNorm would be meaningless.

Additional reasons that reinforce the choice: transformers are trained with very large or very small effective batch sizes depending on the hardware and gradient accumulation, and LayerNorm is indifferent; and the activations at a given position vary enormously in scale, which per-token normalization handles naturally.

What normalization actually does. The original “internal covariate shift” explanation has been substantially undermined — Santurkar et al. (2018) showed that injecting noise after BatchNorm, deliberately restoring covariate shift, still leaves the benefit intact. The better-supported account is that normalization smooths the loss landscape, bounding the gradient magnitudes and making the effective Lipschitz constant smaller, which permits larger learning rates and makes optimization less sensitive to initialization. Worth knowing, because a good interviewer may specifically probe whether you still believe the original story.

The math, and what it buys you.

For an activation tensor of shape (batch , features ):

Batch normalization computes one mean and one variance per feature, across the batch:

Layer normalization computes one mean and one variance per sample, across the features:

On a 4x3 example with wildly different feature scales, the difference is visible immediately:

x =
 [[ 1.126  4.736 10.320]
  [ 1.105  3.929 10.181]
  [ 2.304  6.894  9.648]
  [-0.265  3.753 10.021]]

BatchNorm  -> column means [0, 0, 0]        column stds [1, 1, 1]
LayerNorm  -> row means    [0, 0, 0, 0]     row stds    [1, 1, 1, 1]

BatchNorm makes each column standard; LayerNorm makes each row standard.

Good and bad.

The consequences. BatchNorm’s statistics depend on the other examples in the batch, and that single fact creates all of its problems.

It behaves differently at train and test time. During training it uses the batch statistics; at inference there is no batch, so it uses running averages accumulated during training. Train and inference are therefore computing different functions, and a mismatch between the running estimates and the deployment distribution is a classic silent-degradation bug. LayerNorm is identical at train and test — no running statistics, no mode switch.

It degrades with small batches. With batch size 2 the mean and variance estimates are extremely noisy, and normalizing by a noisy variance injects noise into every activation. Anything memory-hungry enough to require batch size 1 or 2 — large models, high-resolution segmentation, video — is where BatchNorm falls apart, which is why GroupNorm exists.

It couples examples in a batch. Examples influence each other’s predictions, which breaks the independence assumption that some algorithms need and interferes with contrastive learning, reinforcement learning, and any setting where you care about a per-example output being a function of that example alone.

It is awkward under distributed training. Correct batch statistics across data-parallel workers require a synchronizing all-reduce every BatchNorm layer (SyncBN), which is a communication cost per layer.

Pre-norm vs post-norm, which is where this question usually goes next. The original transformer put LayerNorm after the residual add: , then normalize. Modern models normalize before the sublayer: . Pre-norm leaves a clean, unnormalized identity path from input to output, so gradients flow to early layers without passing through any normalization, and deep models train stably without a warmup schedule. Post-norm sometimes reaches marginally better final quality but is notoriously fragile past a few dozen layers. Essentially every large model since roughly GPT-2 is pre-norm, and many now use RMSNorm, which drops the mean-subtraction and rescales by the root mean square only — — because the re-centering turns out to contribute little and removing it saves compute.

Follow-up: When would you still use BatchNorm? → Convolutional vision models with reasonable batch sizes, where it remains excellent and where its regularizing noise is a genuine benefit. Its statistics also fold into the preceding convolution’s weights at inference, making it free at serving time — a real advantage LayerNorm does not have.

Why the interviewer asks this. It is the cleanest test of whether you understand a mechanism or have memorized a table. The follow-up “why not BatchNorm in a transformer” has one right answer, and it is about variable sequence length.

Saying it out loud. “Same operation, different axis. BatchNorm normalizes each feature across the batch; LayerNorm normalizes each sample across its features. Everything else follows from that — BatchNorm’s statistics depend on the other examples, so it needs running averages at inference, it breaks with small batches, and it needs syncing across GPUs. Transformers use LayerNorm because sequences have different lengths and get padded, so batch statistics would depend on what else happened to be in the batch and how much padding there was. LayerNorm only looks at one token’s own hidden vector, so it’s invariant to all of that, and it works the same during generation when you’re doing one token at a time.”


Q135: What causes vanishing and exploding gradients, and what actually fixes each?

In 30 seconds. “Both come from backprop multiplying Jacobians layer after layer, so anything not close to one becomes exponential in depth. But vanishing is a structural problem you fix by changing the architecture — ReLU, residuals, He init, normalization. Exploding is a dynamics problem you fix at runtime with gradient clipping and a lower learning rate. Clipping does nothing for vanishing; residuals don’t stop an explosion.”

The short version.

VanishingExploding
Naturestructural — signal cannot reach early layersdynamics — the optimizer took too big a step
Typical causessaturating activations, too-small init, long recurrencestoo-large init, spectral radius > 1, high LR, one bad batch
FixesReLU/GELU, residual connections, He/Xavier init, normalization, LSTM gatinggradient clipping by global norm, lower LR, warmup, fix the data
Diagnosisearly-layer grad norms orders of magnitude below late layersspike in global norm, then a loss spike or NaN
Still a live problem in transformers?largely solved by pre-norm residualsvery much alive at scale

Why it works.

The mechanism. Backprop through layers multiplies Jacobians:

If the typical singular value of each factor is , the gradient magnitude scales as . Anything other than is an exponential in depth.

The math, and what it buys you.

Measured, propagating a gradient back through 50 random layers of width 100 with weights scaled by a gain factor:

gain=0.5 : ||grad|| after 10 layers=7.487e-04, 30=7.887e-10, 50=5.402e-16
gain=1.0 : ||grad|| after 10 layers=1.023e+00, 30=8.485e-01, 50=1.207e+00
gain=2.0 : ||grad|| after 10 layers=7.621e+02, 30=5.530e+08, 50=3.822e+14

A factor-of-two error in the initialization scale is the difference between a gradient of and one of . Nothing else in training is this sensitive.

Vanishing gradients — causes. Saturating activations are the historical cause: has a maximum of exactly at , so even in the best case ten sigmoid layers multiply the gradient by at most , and thirty layers by . That is not an edge case, that is the best case. Tanh is better () but still saturates. The second cause is initialization with weights too small. The third, in RNNs, is repeated multiplication by the same recurrent matrix over hundreds of timesteps, where the effective depth is the sequence length.

Good and bad.

Vanishing gradients — fixes. These are structural.

Non-saturating activations. ReLU has derivative exactly 1 on the positive side, so it does not attenuate. This was the single biggest unlock. GELU and SiLU are the modern smooth variants.

Residual connections. makes the Jacobian , so the identity term guarantees a path along which the gradient reaches the input undiminished regardless of depth. This is why 100-layer networks became trainable, and it is the most important item on the list.

Variance-preserving initialization. He initialization, , is derived to keep activation variance constant through ReLU layers (the factor 2 compensates for ReLU zeroing half the inputs); Xavier/Glorot, , is the tanh equivalent. These target exactly the condition above.

Normalization layers. Batch/layer norm rescale activations back to unit variance at every layer, which prevents the compounding drift.

Gating, for recurrent models. The LSTM cell state has derivative with respect to ; when the forget gate is near 1 the gradient passes through unattenuated. It is a residual connection in time.

Exploding gradients — causes. Weights initialized or grown too large; recurrent matrices with spectral radius greater than 1; a learning rate high enough to push the model into a sharp region where the loss curvature is extreme; and occasionally a single pathological batch — a duplicated document, corrupted labels, a numerical overflow in a loss term.

Exploding gradients — fixes. These are largely runtime interventions, and that is the contrast the interviewer is after.

Gradient clipping is the primary tool and has no vanishing-gradient counterpart. Clip by global norm: compute over all parameters and, if it exceeds a threshold , rescale . Clipping by global norm preserves the direction of the update, which is why it is preferred over element-wise value clipping, which distorts it. A threshold of 1.0 is standard for LLM training. Note this is a hard cap applied after the fact — it does not prevent the explosion, it survives it.

Lower the learning rate, and use warmup so the early, poorly-conditioned phase does not take a huge step (Q136).

Careful initialization and normalization, which overlap with the vanishing fixes because they both target .

Fix the data. An explosion localized to one step is frequently a bad batch. Log the offending batch indices and inspect them before reaching for a hyperparameter.

The asymmetry, stated plainly. Vanishing gradients are a structural problem — the architecture does not permit signal to reach the early layers — and are fixed by changing the architecture: residuals, ReLU, normalization. Exploding gradients are a dynamics problem — the optimizer took too big a step — and are fixed at runtime by clipping and by learning-rate control. Gradient clipping does nothing whatsoever for vanishing gradients, and adding residual connections does not prevent an explosion. Say that sentence.

Diagnosis. Log the gradient norm per layer, every step. Vanishing shows as early-layer norms orders of magnitude below late-layer norms, with early-layer weights barely moving from initialization. Exploding shows as a spike in the global norm, usually followed by a loss spike or a NaN. If you are already clipping, log the pre-clip norm and the fraction of steps that get clipped — a clip rate that climbs from 1% to 40% is your early warning.

Follow-up: Do transformers still have this problem? → The vanishing side is largely solved by pre-norm residual architecture. Exploding is very much alive at scale: large-model training runs clip at every step as a matter of course, and loss spikes from gradient explosions are one of the main operational hazards of a long run (Q137).

Why the interviewer asks this. Many candidates give one merged answer — “use ReLU, batch norm, and clipping” — without knowing which fixes which. Being able to separate the structural problem from the dynamics problem is the whole point.

Saying it out loud. “Both come from backprop multiplying Jacobians layer after layer, so anything not close to one becomes exponential in depth. Vanishing is structural — sigmoid’s derivative maxes out at 0.25, so ten layers gets you 1e-6 in the best case — and you fix it structurally, with ReLU, residual connections, proper He init, and normalization. Exploding is a dynamics problem, and you fix it at runtime with gradient clipping by global norm and a lower learning rate. That’s the distinction I’d emphasize: clipping does absolutely nothing for vanishing, and residual connections don’t stop an explosion.”


Q136: Why do we need learning-rate warmup, specifically for Adam?

In 30 seconds. “It’s about the second-moment estimate. Adam divides the gradient by the square root of a running average of squared gradients, and with beta-2 at 0.999 that average needs about a thousand steps to settle. At step one it’s degenerate — the bias-corrected update is exactly plus-or-minus the full learning rate, whatever the gradient was. Warmup just keeps the learning rate small until that estimate is trustworthy.”

The short version.

  • Warmup = ramp from ~0 to the target LR over the first few hundred to few thousand steps, then decay.
  • The reason is Adam’s : an EMA with has a ~1000-step averaging window.
  • Bias correction fixes the expectation, not the variance — and the variance is the problem.
  • At step 1 the update is exactly per parameter, direction set by one noisy gradient sample.
  • Two reinforcing, non-Adam reasons: bad conditioning at init, and large-batch training’s large target LR.
  • Practical: linear warmup over 1-5% of steps (500-4,000), then cosine decay to ~10% of peak.

Why it works.

The problem is that is a terrible estimate early on. It is an exponential moving average with , which has an effective averaging window of about steps. At step 10 it has seen ten samples of and is being asked to report their long-run mean. The bias correction fixes the expectation but does nothing about the variance, and the variance is the whole issue: the update divides by , so when happens to come in small, the step is enormous.

This is the argument formalized by RAdam (Liu et al., 2020): the variance of the adaptive learning rate is unbounded in the first few steps, and warmup acts as an implicit variance-reduction heuristic. RAdam instead rectifies the term analytically and turns off adaptivity until the variance estimate is trustworthy, which makes it work without warmup — a useful thing to be able to name.

Two reinforcing reasons that are not Adam-specific. At initialization the parameters are random, so the loss surface is poorly conditioned and the gradients are large and uninformative; a big step early moves the model into a bad basin it may never leave. And large-batch training uses a large target learning rate to compensate for the reduced gradient noise, which makes the early-step overshoot correspondingly more destructive — warmup is essentially mandatory once batch size gets large, and its length typically scales with batch size.

The math, and what it buys you.

The Adam update. With , :

The step size in each coordinate is — the gradient divided by its own root-mean-square. Adam is adaptive precisely because of that division.

The pathological case is step 1. Then and ; after bias correction and , so the update is exactly . Every parameter moves by the full learning rate, with only the sign of a single noisy gradient sample deciding the direction. Simulating a pure-noise coordinate whose true gradient mean is zero, and measuring the magnitude of Adam’s update over time:

step     1: E|update|=1.0000  sd=0.0000  p99=1.0000
step     2: E|update|=0.6340  sd=0.3155  p99=1.0012
step     5: E|update|=0.3804  sd=0.2448  p99=0.9199
step    10: E|update|=0.2686  sd=0.1906  p99=0.7664
step    50: E|update|=0.1865  sd=0.1371  p99=0.5808
step  1000: E|update|=0.1811  sd=0.1374  p99=0.5921

The steady-state update magnitude for a pure-noise coordinate is about . At step 1 it is — a 5.5x overshoot, in a direction that is pure noise. By step 50 it has converged. That gap, over the first tens to hundreds of steps, is exactly what warmup exists to cover.

Good and bad.

Practical parameters. Linear warmup over roughly 1% to 5% of total steps, commonly 500 to 4,000 for a large model, then cosine decay to about 10% of peak. Transformers are the standard case; the original paper’s -scaled schedule with 4,000 warmup steps was for post-norm, which is genuinely untrainable without it. Pre-norm architectures need warmup much less — that is one of the main reasons pre-norm won — but essentially everyone still uses it, because it is nearly free and removes a whole class of failure.

Follow-up: Would warmup help plain SGD? → Somewhat, for the poor-conditioning-at-init reason and especially with large batches, but far less, because SGD’s step is proportional to the gradient magnitude rather than normalized by it. When SGD’s early gradients are noisy the steps are just noisy; when Adam’s are noisy the division by a badly-estimated makes them noisy and full-sized.

Why the interviewer asks this. “Because everyone does it” is the failing answer. The variance of at small is the real one, and it demonstrates you have read the update rule rather than just called the optimizer.

Saying it out loud. “It’s about the second-moment estimate. Adam divides the gradient by the square root of a running average of squared gradients, and with beta-2 at 0.999 that average needs about a thousand steps to settle. Early on it’s high-variance, and when it comes in small you take a huge step. At step one it’s degenerate — the bias-corrected update is exactly plus-or-minus the full learning rate, no matter what the gradient was. I simulated it: for a pure-noise coordinate, the steady-state update is about 0.18 times the learning rate, and at step one it’s 1.0. So warmup just keeps the learning rate small until that estimate is trustworthy.”


Q137: What does a loss spike mean and what do you do about it?

In 30 seconds. “Usually it’s a bad batch — corrupted text or a document of repeated tokens — producing a huge gradient and a step that damages the model. The way you tell it apart from plain gradient explosion is that the pre-clip gradient norm spikes a step or two before the loss does, which is why you log pre-clip and not post-clip. First move is to watch: a lot of spikes self-heal.”

The short version.

CauseThe tell
A bad batch (most common)Tied to a specific step; doesn’t reproduce with a different data order
Gradient explosionPre-clip gradient norm spikes 1-2 steps before the loss
LR too high for current curvatureRepeated spikes at regular intervals; one right after warmup ends
fp16 numerical overflowLoss scale being repeatedly halved; NaNs
Adam state pathology (tiny stale )Long-idle parameter suddenly takes an enormous step
Attention logit growthSoftmax drifting toward one-hot; fixed by QK-norm

The playbook, in order: watch → roll back and skip → investigate the skipped batches → structural mitigations.

Why it works.

A loss spike is a sudden jump in training loss — often by several nats, sometimes to NaN — after a period of stable descent. In small-scale training it is a curiosity. In large-model pretraining it is one of the main operational hazards, because a single unrecovered spike can waste days of accelerator time.

What it means, in order of how often it is the cause.

A bad batch. By far the most common. A batch containing corrupted text, a document of repeated tokens, mislabelled data, or an extreme outlier produces a huge gradient, the optimizer takes a large step, and the model is damaged. The tell is that the spike is tied to a specific step, and re-running from a checkpoint with a different data order does not reproduce it.

Gradient explosion. The gradient norm blows up (Q135), the step is enormous, and the model is thrown out of its basin. The tell is the pre-clip gradient norm spiking one or a few steps before the loss does — which is why you log pre-clip norm, not post-clip.

Learning rate too high for the current curvature. Training enters a sharper region and the step size that was fine yesterday is now past the stability edge. The tell is repeated spikes at roughly regular intervals, and a spike right after a warmup ends or a schedule changes.

Numerical precision. In fp16, activations or attention logits overflow to inf and produce NaN. The loss-scaling machinery normally catches this, but a persistent overflow means the scale is being repeatedly halved. bf16 has the same exponent range as fp32 and largely eliminates this class of failure; if you are training in fp16 at scale, this is your prime suspect.

Adam state pathology. If a parameter’s gradient has been near zero for a long stretch, its accumulator decays toward zero; when a real gradient finally arrives, dividing by a tiny produces an enormous step. This is a known contributor to spikes in large runs, and it is why some practitioners lower to for large-model training — the shorter window is more responsive and less prone to a stale, tiny . The value matters here too.

Attention logit growth. In large transformers, the pre-softmax logits can grow without bound during training, driving the softmax toward one-hot, gradients toward zero, and the numerics toward the edge. QK-normalization — applying a norm to the query and key vectors before the dot product — is the standard mitigation and is now common in large model training recipes.

Good and bad.

What to do, in the order you should do it.

First, does it recover? Many spikes self-heal within a few hundred steps and cost you nothing but nerves. Watch before you act. A spike that recovers to the previous trajectory needs no intervention.

If it does not recover, roll back and skip. The standard playbook: restore the most recent checkpoint from before the spike, skip the batches that produced it (typically the surrounding few hundred), and resume. This is not a hack — it is documented practice in the PaLM and OPT training reports, and it works because the cause is usually data-specific. Crucially, restore the optimizer state along with the weights, since damaged and will re-cause the problem.

Then investigate the skipped batches. If they contain garbage, fix the data pipeline; you have found a real bug and it will recur.

Structural mitigations, if spikes are frequent. Lower the peak learning rate or extend warmup. Tighten gradient clipping. Switch fp16 to bf16. Lower from to . Add QK-norm or z-loss (a small penalty on the log-partition function of the softmax, which keeps logits from drifting). Improve data cleaning and shuffling so that similar documents do not cluster in a batch.

What not to do. Do not simply continue and hope — an unrecovered spike frequently means the model has lost capability it will spend many steps re-earning, and sometimes never recovers. Do not diagnose without the pre-clip gradient norm; post-clip norm is capped by construction and shows you nothing.

Monitoring that makes this tractable. Per-step: loss, pre-clip global gradient norm, clip rate, learning rate, and — if using fp16 — the loss scale. Per-N-steps: per-layer gradient norms, weight norms, and the max absolute activation. Checkpoint often enough that rolling back costs an acceptable amount of compute; for a long run that usually means every few hundred to few thousand steps. Retain the exact data ordering, and the RNG seed, so a rollback is reproducible and you can identify which batches to skip.

Follow-up: Is a validation loss spike different from a training loss spike? → Yes, and importantly so. A training spike is an optimization event. Validation loss rising while training loss keeps falling is overfitting, and the response is regularization or early stopping, not a rollback. Validation loss rising in step with a training spike is the optimization event showing through — treat it as one incident.

Why the interviewer asks this. It is an operations question dressed as a theory question. Anyone who has babysat a real training run has the rollback-and-skip playbook and the pre-clip-gradient-norm habit; anyone who has not, does not.

Saying it out loud. “Usually it’s a bad batch — corrupted text or a document of repeated tokens — producing a huge gradient and a step that damages the model. Second most common is straightforward gradient explosion, and the way you tell is that the pre-clip gradient norm spikes a step or two before the loss does, which is why you log pre-clip and not post-clip. First thing I do is watch: a lot of spikes self-heal in a few hundred steps. If it doesn’t recover, roll back to the last checkpoint including the optimizer state, skip the offending batches, and resume — that’s the standard playbook from the PaLM and OPT reports. Then go look at what was in those batches, because it’s usually a real data bug.”


Modern LLM Systems

Q138: What does FlashAttention actually do?

In 30 seconds. “The thing people get wrong is calling it an approximation — it’s exact, and it doesn’t reduce FLOPs at all. What it reduces is memory traffic between HBM and on-chip SRAM. Standard attention writes the full n-by-n matrix to HBM and reads it back twice; FlashAttention tiles the computation so blocks fit in SRAM and never materializes that matrix.”

The short version.

ClaimTrue?
It’s an approximation of attentionNo — output is exact
It reduces computeNo — same FLOPs
It reduces memory traffic HBM ↔ SRAMYes — this is the whole point
It reduces memory footprintYes — in sequence length
It’s an alternative to sparse/linear attentionNo — those change the math; this changes the schedule
It composes with GQA/MQAYes — orthogonal (they shrink the KV cache)

Start by killing the common wrong answer, because interviewers ask this question specifically to see whether you have it. The wrong answer is “FlashAttention is an efficient approximation of attention that reduces the quadratic complexity.” It is not an approximation and it does not reduce FLOPs. FlashAttention computes exactly the same output as standard attention, bit-for-bit equivalent up to floating-point reassociation, and it performs the same arithmetic. What it reduces is memory traffic — the number of reads and writes between the GPU’s high-bandwidth memory (HBM) and its on-chip SRAM.

Why it works.

Why memory traffic is the bottleneck. A modern GPU has roughly two orders of magnitude more arithmetic throughput than memory bandwidth. Attention is a memory-bound operation: the arithmetic per byte moved is low, so the matrix units sit idle waiting for data. It is a chef with an enormous kitchen and one narrow doorway to the pantry. Standard attention makes this worse than necessary by materializing intermediates in HBM:

S = Q K^T          write n x n to HBM
P = softmax(S)     read n x n, write n x n
O = P V            read n x n

Three round trips of an matrix per head.

What FlashAttention does instead. It tiles , , and into blocks that fit in SRAM, and computes the output for a block of queries by streaming through blocks of keys and values, accumulating the result — never writing the matrix anywhere. Two techniques make this possible.

Online softmax. Softmax normally needs the full row before it can normalize. The online formulation keeps a running maximum and a running sum of exponentials , and when a new block arrives with its own , rescales the accumulated output by before adding the new contribution. This is algebraically exact — it is the same numerically-stable max-subtraction trick everyone already uses, applied incrementally. This is the mathematical core of the method.

Recomputation instead of storage in the backward pass. Rather than saving the attention matrix for backprop, FlashAttention saves only the per-row softmax statistics — which are — and recomputes the attention blocks on the fly. It spends extra FLOPs to avoid memory traffic, which is exactly backwards from ordinary optimization intuition and exactly right on this hardware.

The math, and what it buys you.

The size of the materialized matrix is the problem, and the numbers say why long context was impossible before this:

seq=1024:   n^2 fp16 per head = 0.0021 GB;  x32 heads =    0.07 GB
seq=8192:   n^2 fp16 per head = 0.1342 GB;  x32 heads =    4.29 GB
seq=131072: n^2 fp16 per head = 34.36 GB;   x32 heads = 1099.51 GB

At 128K context the attention matrices alone would require a terabyte. That is a memory problem, not a compute problem — which is precisely why the fix is a memory-schedule fix.

The results. Memory goes from to in sequence length. HBM accesses drop from to roughly where is the SRAM size — still quadratic in , note, but divided by a large constant. The reported speedups are around 2-4x wall-clock for attention with substantially reduced memory.

Good and bad.

Good: the output is exact, so there is no accuracy trade-off to evaluate, no hyperparameter, and nothing to validate — you turn it on and the model is the same model. That is the property that made it universal, whereas approximate attention methods (Linformer, Performer, sparse patterns) required you to accept a quality cost and mostly did not stick.

Bad / limits: What it is not. It does not reduce the asymptotic compute. It does not change the model, the weights, or the output. It is not an alternative to sparse or linear attention — those change the math; this changes the memory schedule. It is orthogonal to and composable with multi-query and grouped-query attention, which reduce the KV cache rather than the attention computation.

The lineage: FlashAttention-2 improved the work partitioning across thread blocks and warps and cut non-matmul FLOPs; FlashAttention-3 targeted Hopper-generation hardware with asynchrony and FP8 support. Time-sensitive: the specific speedup multiples and the current version number date quickly and are hardware-specific — quote the mechanism, not a benchmark figure.

Follow-up: If it does not reduce FLOPs, why is it faster? → Because attention was never FLOP-limited. The matrix units were stalled waiting on HBM. Removing the stalls raises achieved utilization, so the same arithmetic finishes sooner. This is why the backward pass profitably recomputes: extra FLOPs are cheap, extra memory traffic is not.

Why the interviewer asks this. It is a precise filter for whether you understand the memory hierarchy of a GPU or are pattern-matching on “efficient attention.” Saying “it’s exact and it doesn’t reduce FLOPs” in the first sentence is the strongest possible opening.

Saying it out loud. “The thing people get wrong is calling it an approximation — it’s exact, and it doesn’t reduce FLOPs at all. What it reduces is memory traffic between HBM and on-chip SRAM. Standard attention writes the full n-by-n matrix out to HBM and reads it back twice, and at long context that matrix is enormous. FlashAttention tiles the computation so blocks fit in SRAM and uses an online softmax with a running max and running sum, so it never materializes the matrix. In the backward pass it even recomputes attention rather than storing it — spending extra FLOPs to save memory traffic, because attention was memory-bound, not compute-bound.”

Sources: FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness


Q139: Explain speculative decoding, including why the output distribution is provably unchanged.

In 30 seconds. “Decoding is memory-bound — you read all the weights to produce one token — so a small draft model proposes about five tokens and the big model verifies all of them in one forward pass, which costs about the same as producing one. You accept each draft token with probability min(1, p/q), and on the first rejection you resample from the normalized positive part of p minus q. That’s exactly lossless, because min(p,q) + max(0, p−q) = p.”

The short version.

  1. The draft model autoregressively generates candidate tokens (typically to ). This is cheap because the draft is small.
  2. The target model runs one forward pass over all candidates in parallel, which costs about the same as generating one token, because that single pass is bounded by reading the weights, not by the arithmetic. This yields for every position .
  3. Walk left to right. Accept token with probability .
  4. On the first rejection at position , discard and everything after it, and sample a replacement from the residual distribution .
  5. If all are accepted, the target’s own distribution at position gives you one extra free token.

So each round produces between 1 and tokens for roughly the cost of one target forward pass.

Why it works.

Autoregressive generation is memory-bound in exactly the way attention is. Producing one token requires reading every weight of the model from HBM, and the arithmetic done with those weights — a batch of one token — is trivial. The GPU is idle most of the time. If you could process several tokens per weight-read, you would get them almost for free.

The math, and what it buys you.

The correctness proof. This is the part that separates a real answer from a sketch. Claim: the token emitted at each position is distributed exactly as .

Let . The probability that token is drawn from the draft and accepted is

The total acceptance probability is therefore , which is , one minus the total variation distance. On rejection — probability — we sample from the residual , and the normalizer is exactly because .

Put the two paths together:

And for any reals — if it is ; if it is . So . Exactly the target distribution, for any draft model whatsoever. The draft’s quality affects only the speed, never the output distribution — a deliberately terrible draft model gives you no speedup and a still-perfectly-correct sample.

Verified empirically over 4,000,000 draws with a random 6-token vocabulary:

target p : [0.23676 0.10028 0.01523 0.00614 0.30230 0.33928]
empirical: [0.23671 0.10031 0.01531 0.00618 0.30212 0.33936]
max abs err: 1.77e-4
acceptance rate: 0.51380    theoretical sum min(p,q) = 0.51391

Both the distribution and the predicted acceptance rate match.

Speedup arithmetic. With per-token acceptance probability (assumed independent), the expected number of tokens per round is a truncated geometric sum:

alpha=0.7 k=4: 2.773      alpha=0.8 k=4: 3.362      alpha=0.9 k=4: 4.095
alpha=0.7 k=8: 3.199      alpha=0.8 k=8: 4.329      alpha=0.9 k=8: 6.126

Note the diminishing returns in : at , going from to buys only while doubling the draft cost. The optimal falls as falls. Real end-to-end speedups are typically 2-3x, less than these numbers suggest because the draft model’s own time and the verification overhead are not free.

Good and bad.

When it works and when it does not. It works when the draft agrees with the target often — which means on predictable text, code boilerplate, and formatting, and much less on genuinely hard reasoning tokens. It requires the draft to share the target’s tokenizer. It hurts throughput under high batch load, because at large batch sizes the GPU is already compute-saturated and the wasted draft work is a real cost; speculative decoding is a latency optimization for low-batch serving, not a throughput one. That trade-off is the practical point worth volunteering.

Variants worth naming: Medusa attaches extra prediction heads to the target model itself instead of using a separate draft; EAGLE drafts in feature space rather than token space for higher acceptance; self-speculation uses a subset of the target’s own layers as the draft; and n-gram or prompt-lookup drafting simply copies from the prompt, which works remarkably well for summarization and code editing where output overlaps input.

Follow-up: Does this work with greedy decoding? → Yes, and it is simpler: accept a draft token if it equals the target’s argmax, reject at the first mismatch and take the target’s token. The same “output is unchanged” guarantee holds trivially. The general rejection-sampling scheme is what extends it to temperature sampling, top-p, and any other sampler applied consistently to both models.

Why the interviewer asks this. Anyone can describe draft-and-verify. The acceptance rule and the residual distribution are what make it lossless, and being able to show on a whiteboard is a decisive answer.

Saying it out loud. “Decoding is memory-bound — you read all the weights to produce one token — so a small draft model proposes maybe five tokens and the big model verifies all of them in one forward pass, which costs about the same as producing one. You accept each draft token with probability min of one and p over q, and on the first rejection you resample from the normalized positive part of p minus q. The reason that’s exactly lossless is that accepting gives you min(p,q) and the rejection branch gives you max(0, p minus q), and those two always sum to p. So the draft model only affects your speed, never your output distribution. I verified it on four million samples — matches to about one part in ten thousand.”

Sources: Fast Inference from Transformers via Speculative Decoding, Accelerating Large Language Model Decoding with Speculative Sampling


Q140: How does knowledge distillation work, and when is it the right call?

In 30 seconds. “You train a small model to match a big model’s output distribution instead of the hard labels. That works because the soft distribution carries what Hinton called dark knowledge — it says a seven looks a bit like a one and nothing like a cat, which one-hot labels throw away. For LLMs the version people actually run is sequence-level: have the teacher generate a corpus and fine-tune the student on it.”

The short version.

VariantWhat it matchesWhere it’s used
Response distillationOutput distributionsThe classic Hinton setup
Feature distillationIntermediate hidden states (via a learned projection)DistilBERT, TinyBERT
Attention distillationAttention mapsTransformer compression
Sequence-level distillationTeacher-generated text, plain cross-entropyDominant for generative LLMs today
Self-distillationSame architecture, teacher = studentPure regularization effect

Why it works.

The mechanism. A one-hot label tells the student “this is a 7.” The teacher’s distribution says “0.90 seven, 0.07 one, 0.02 nine, 0.001 cat” — which additionally encodes that sevens resemble ones, somewhat resemble nines, and are nothing like cats. Hinton called this the dark knowledge: a learned similarity structure over the output space that the hard label throws away. It also acts as a per-example difficulty signal, since an ambiguous example gets a high-entropy teacher distribution and is therefore softly weighted down.

The math, and what it buys you.

The problem is that a well-trained teacher’s distribution is nearly one-hot, so the dark knowledge is buried in probabilities of and contributes nothing to the gradient. The fix is temperature: divide the logits by before the softmax,

which flattens the distribution and surfaces the relative ordering of the non-target classes. The student is trained with the same temperature and the combined loss

The factor is not decoration: softening the distribution scales the gradients of the KL term by roughly , so multiplying by keeps the two loss terms comparably weighted as you tune . Typical values are between 2 and 5 and around 0.5 to 0.9. Forgetting the is a common bug that makes appear to have no effect.

Variants. Response distillation matches output distributions, as above. Feature distillation additionally matches intermediate hidden states, usually through a learned projection to reconcile widths — this is what DistilBERT and TinyBERT do, and it transfers more signal than logits alone. Attention distillation matches attention maps. Sequence-level distillation, the dominant form for generative LLMs, simply has the teacher generate a large corpus of outputs and fine-tunes the student on them with ordinary cross-entropy — this is what “training on synthetic data from a bigger model” means, and it is by far the most commonly deployed variant today. Self-distillation, where student and teacher are the same architecture, still improves accuracy, which is a strong hint that the regularization effect matters independently of compression.

Good and bad.

When it is the right call. When you have a latency or cost budget that the large model cannot meet, and you have a large pool of unlabelled in-domain data — distillation needs inputs, not labels, so unlabelled data is enough and the teacher supplies the targets. When you need to specialize: a 7B student distilled from a frontier model on a single task routinely matches or beats the teacher on that task while being an order of magnitude cheaper, because it does not have to be good at everything. When you need on-device or edge deployment. And when you already have an expensive ensemble in production and want one model with most of its quality.

When it is not. When you do not have enough unlabelled data covering the deployment distribution — the student only learns the teacher’s behaviour where you query it, so coverage gaps become silent failures. When you need broad general capability, since capacity is a real constraint and a small student cannot absorb a frontier model’s full range. When quantization or pruning would meet your budget more cheaply — those need no training run and no data, so try them first and reach for distillation when they are not enough. And when the teacher’s licence forbids using its outputs to train a competing model, which is a genuine and frequently overlooked constraint in the API era.

Practical notes. Distill on the deployment distribution, not on a generic corpus — the student inherits the teacher’s behaviour exactly where you sampled. Distillation compresses biases and hallucinations faithfully along with the capability, so the student inherits the teacher’s failure modes. Combine with quantization for compounding gains. And evaluate the student on your task metrics, not on the KL divergence, which can look excellent while the behaviour you care about has degraded.

Follow-up: Can a student beat its teacher? → On a narrow task, routinely — specializing frees capacity that the teacher spends on generality, and the teacher’s soft targets act as a regularizer. Across general capability, essentially never; capacity binds.

Why the interviewer asks this. Distillation is the standard answer to “make this cheaper,” so they want to know if you understand why soft targets help rather than just that they do — and whether you know that the modern generative version is mostly “fine-tune on teacher-generated text.”

Saying it out loud. “You train a small model to match a big model’s output distribution instead of the hard labels. The reason that works is the soft distribution carries what Hinton called dark knowledge — it tells you a seven looks a bit like a one and nothing like a cat, which one-hot labels throw away. You raise the softmax temperature to surface that structure and scale the loss by T-squared so the gradient magnitudes stay comparable. For LLMs the version people actually run is sequence-level: have the teacher generate a big corpus and fine-tune the student on it. It’s the right call when you’ve got a latency budget and lots of unlabelled in-domain data — but I’d try quantization first, since it needs no training run at all.”


Q141: What is chain-of-thought, when does it help, and when does it hurt?

In 30 seconds. “A transformer does a fixed amount of computation per token, so if a problem needs more sequential steps than the model has layers, it can’t do it in one pass. Generating intermediate tokens buys serial compute — each token gets its own forward pass and can attend to everything before it. So it helps wherever a human would want a scratchpad, and hurts on tasks that are holistic rather than deliberate.”

The short version.

HelpsHurts
Multi-step arithmetic and word problemsTasks where deliberation degrades humans too (verbal overshadowing)
Symbolic manipulation and logicSimple factual retrieval — adds latency and a chance to talk yourself out of it
Multi-hop questions chaining retrieved factsLatency- and cost-sensitive paths (output tokens dominate both)
Planning and code generationWhen it manufactures false confidence — the chain may not be the real reason
Anything where a human wants a scratchpadSmall models: plausible-looking wrong reasoning, then commitment to it

Chain-of-thought (CoT) prompting is asking a model to produce intermediate reasoning steps before its final answer, instead of emitting the answer directly. In its original zero-shot form it is literally the phrase “Let’s think step by step”; in the few-shot form you provide exemplars that show worked reasoning.

Why it works.

The mechanical explanation, which is the one to give. A transformer performs a fixed amount of computation per token: a fixed number of layers, each with a fixed width. If a problem requires more sequential computation than the depth of the network provides, the model cannot do it in one forward pass, full stop. Generating intermediate tokens is how a transformer buys additional serial compute — each generated token gets its own full forward pass, and it can attend to the tokens produced before it. Chain-of-thought converts a depth-limited problem into a length-unlimited one, effectively turning the model into a machine with a scratchpad. That framing also predicts the empirical finding that even semantically meaningless filler tokens can help slightly on some tasks, and it explains why CoT does essentially nothing on tasks that were already within a single forward pass.

A secondary effect: intermediate steps condition subsequent generation, so having written “the total is 47” makes the model far more likely to use 47 consistently downstream than if it had to hold it implicitly.

When it helps. Multi-step arithmetic and word problems. Symbolic manipulation and logic. Multi-hop questions that require chaining retrieved facts. Planning and code generation, where laying out the structure first improves the output. Anything where a human would need a scratchpad is the reliable heuristic. The original result was also strongly emergent with scale — small models get little benefit or are actively harmed, because they generate plausible-looking reasoning that is wrong, and then commit to it.

Good and bad.

When it hurts. This is the more interesting half and where the good answers separate.

Tasks where deliberation degrades human performance too. This is a real, documented result: “Mind Your Step (by Step): Chain-of-Thought can Reduce Performance on Tasks where Thinking Makes Humans Worse” (Liu et al., 2024) draws on the psychology literature on verbal overshadowing and shows that on tasks in that family — implicit statistical learning, facial recognition, classifying data containing exceptions — CoT causes substantial drops. The intuition is that these tasks depend on holistic pattern recognition, and forcing an explicit verbal account of the decision disrupts it. Naming that paper is a strong move in an interview.

Simple factual retrieval. If the answer is a single lookup, reasoning adds latency, cost, and the opportunity to talk yourself out of a correct answer.

Latency- and cost-sensitive paths. CoT multiplies output tokens, and output tokens dominate both latency and cost in most APIs. A 10x token increase for a 3% accuracy gain is a bad trade in a real-time product.

When it manufactures false confidence. CoT produces reasoning that reads as rigorous whether or not it is. Worse, the stated reasoning is not necessarily the actual reason for the answer — models have been shown to produce a plausible chain that rationalizes an answer determined by a bias in the prompt, without mentioning that bias. Treating a chain of thought as a faithful explanation is a genuine safety error, and interviewers at labs care about this specifically.

When errors compound. Each step is a chance to go wrong, and an early arithmetic slip propagates confidently to the end. Self-consistency — sampling several chains and majority-voting the final answers — is the standard mitigation and works because independent errors diverge while correct reasoning converges.

The 2026 context, which you should flag as time-sensitive. Explicit CoT prompting has been substantially absorbed into models themselves: reasoning models trained with reinforcement learning to produce long internal chains before answering do this natively, and telling such a model to “think step by step” is redundant or mildly harmful. The durable content of this answer is the mechanism — intermediate tokens buy serial compute — and the failure modes, both of which apply equally to a model’s internal reasoning. What dates quickly is the prompting advice.

Follow-up: How do you know whether the reasoning is faithful? → You largely cannot from the text. Perturbation tests help: change a step in the chain and see whether the answer changes as it should; inject a biasing cue and check whether the chain mentions it. Faithfulness of reasoning traces is an open research problem, and the honest answer is to treat the chain as an artifact that improves accuracy, not as an explanation you can audit.

Why the interviewer asks this. Almost everyone can define chain-of-thought. Far fewer can explain it as serial-computation-through-token-generation, and fewer still know that it measurably hurts on a characterizable class of tasks.

Saying it out loud. “A transformer does a fixed amount of computation per token, so if a problem needs more sequential steps than the model has layers, it just can’t do it in one pass. Generating intermediate tokens buys serial compute — each token gets its own forward pass and can attend to everything before it. That’s why it helps on anything where a human would want a scratchpad. Where it hurts is tasks that are holistic rather than deliberate — there’s a nice paper showing CoT degrades performance on exactly the tasks where overthinking hurts humans too. And I’d push back on treating the chain as an explanation. Models will produce a clean-looking chain that rationalizes an answer they reached for a different reason entirely.”

Sources: Mind Your Step (by Step): Chain-of-Thought can Reduce Performance on Tasks where Thinking Makes Humans Worse


Q142: What is an agent, and how is it actually different from a chatbot with tools?

In 30 seconds. “The difference is who’s driving the loop. With a chatbot that has tools, my application code controls the flow — model calls a tool, I execute it, hand back the result, done. With an agent, the model decides what to do next and when it’s finished, so the number of steps isn’t known up front. That buys flexibility and costs predictability, cost control, and testability.”

Core differences.

Chatbot with toolsAgent
Who controls the loopThe applicationThe model
Number of stepsFixed or shallowly boundedUnbounded a priori — the model decides when to stop
State across stepsReturns to the user each turnAccumulates; must be managed against the context window
Error recoveryYour error-handling codeThe model’s job — it reads the error as an observation
Side effectsOne visible action per turnMany actions the user never individually approved

Why it works.

In a chatbot with tools — sometimes called single-turn function calling — the application controls the flow. The user asks something, the model may emit a tool call, the application executes it, feeds the result back, and the model produces a reply. The number of steps is fixed or shallowly bounded, the control flow is written in your code, and every turn returns to the user. It is a request-response system with a model in the middle.

In an agent, the model controls the flow. It is placed in a loop: observe the current state, decide what to do next, act, observe the result, repeat, and decide for itself when the task is done. The number of iterations is not known in advance, the sequence of tools is not predetermined, and control does not return to the user between steps. The application supplies tools, a goal, and guardrails; the model supplies the plan.

That shift has four concrete consequences, and naming them is what makes the answer more than a definition.

Autonomy over the number of steps. Because the model decides when to stop, the cost and latency of a request are unbounded a priori. This is the source of most of the operational difficulty (Q143).

State that accumulates across steps. An agent must carry forward what it has learned — files read, results computed, hypotheses ruled out — across many turns. Managing that state against a finite context window is the central engineering problem of agent building, and it is why compaction, summarization, and external memory (scratchpad files, vector stores) are standard rather than optional.

Error recovery is the model’s job. A chatbot’s failed tool call is handled by your error-handling code. An agent sees the error message as an observation and must decide whether to retry, try a different tool, or give up. That means an agent’s competence is bounded as much by its ability to interpret failures as by its ability to plan.

Actions have consequences the user did not individually approve. A chatbot with tools acts once, visibly. An agent may take fifty actions, some of them writing to systems, before returning. This is why permission models, allow-lists of side-effecting tools, and human-in-the-loop checkpoints on irreversible actions are architectural requirements rather than nice-to-haves.

Good and bad.

The spectrum, and why a purist definition is wrong. In practice these are not two categories but a continuum, and it is worth saying so: fixed prompt chain, then a router that picks one of paths, then a bounded loop with a step cap, then an open-ended loop, then multi-agent systems where one agent delegates to others. Each step up the ladder buys flexibility and costs predictability, cost control, and testability.

The genuinely important corollary: prefer the least agentic thing that solves the problem. If your task is “extract fields from an invoice,” that is a prompt, not an agent — an agent will cost 20x as much, take 30 seconds instead of 2, and fail in more interesting ways. Agents earn their overhead when the sequence of steps genuinely cannot be known in advance: debugging, open-ended research, and any task where step depends on what step discovered. Interviewers ask this question partly to see whether you reach for the most complex architecture by default.

Follow-up: What about multi-agent systems? → They add a second axis — delegation between models — and are worth it mainly when subtasks are genuinely parallel, or need isolated context windows so one subtask’s clutter does not pollute another’s. They also multiply the failure modes: error compounding across handoffs and information loss at the interface between agents are well-documented problems, and a single agent with good tools beats a poorly-decomposed multi-agent system most of the time.

Why the interviewer asks this. “Agent” is the most overloaded word in the field right now, and the question is really testing for architectural judgment: can you say what the actual technical difference is, and do you know when not to build one?

Saying it out loud. “The difference is who’s driving the loop. With a chatbot that has tools, my application code controls the flow — model calls a tool, I execute it, hand back the result, done. With an agent, the model decides what to do next and when it’s finished, so the number of steps isn’t known up front. That gets you flexibility for tasks where you genuinely can’t script the sequence, like debugging. But it costs you predictability, cost control, and testability, so my default is to build the least agentic thing that solves the problem. If the steps are knowable, write them down — don’t make the model rediscover them every time.”


Q143: How do you stop an agent loop from running forever or costing unbounded money?

In 30 seconds. “Layered limits, all enforced in the harness and never in the prompt — a prompt is a request, a harness limit is a guarantee. Max steps, wall-clock deadline, token budget, and two dollar caps: one per run and one rolling per-hour on the account, because a per-run cap does nothing when a bug is spawning a thousand runs. Then loop detection, which in practice is more useful than the step cap.”

The short version.

LayerMechanismBlind spot it doesn’t cover
Hard limitsMax iterations, wall-clock deadline, token budget, tool-call countsOne enormous tool result blowing context and cost
Cost limitsPer-run cap and rolling per-hour account cap; alert on spend rateSlow, steady, useless progress
Loop detectionHash (tool, normalized args); cycle and no-progress checksVaried but pointless actions
Progress requirementsStated goal, checked-off subgoals, escalate after idle steps
Structural limitsSub-agent recursion depth and spawn count; retries with terminal errors
Graceful degradationReturn partial work with what was and wasn’t completed

Why it works.

An agent decides for itself when to stop, so “when to stop” is a property of your system, not of the model. Every production agent needs layered limits, and the right answer here is a list of independent mechanisms — because any single one can be defeated by a sufficiently confused model.

Hard limits, which are non-negotiable. A maximum iteration count, checked in your loop, not requested in the prompt. A wall-clock deadline. A token budget covering both input and output across the whole run. A tool-call count, and a per-tool count so one flaky API cannot be hammered. All of these must be enforced in the orchestration code, because a model asked to “use at most 10 steps” will exceed it. Choose the numbers from the observed distribution of successful runs — if 95% of successful runs finish in 12 steps, cap at 25, not 200. A cap far above the real distribution is not a safety limit, it is an expensive way to fail.

Cost limits, stated in money. Track spend per run and per user or tenant, and hard-stop at a ceiling. Two ceilings are useful: a per-run cap that kills the individual run, and a rolling per-hour account-level cap that catches a bug that has spawned a thousand runs — this is the one that actually saves you, because a per-run cap of $2 does nothing when something is launching runs in a tight loop. Alert on the rate of spend, not just the total, so you find out during the incident rather than on the invoice.

Loop detection, which is more useful than a step cap. The characteristic agent failure is not an infinite variety of actions but the same action repeated. Detect it directly: hash each (tool, normalized arguments) pair and flag exact repeats; flag a cycle where the last actions repeat a previous window; detect a no-progress condition where state has not changed across several steps. On detection, do not just kill the run — inject an observation telling the model it is repeating itself and to try something different. That often recovers, and it is much better than failing.

Progress requirements. Require the agent to state a goal and check off subgoals; if no subgoal has been completed in steps, escalate. This turns “am I looping?” into a measurable condition rather than a heuristic.

Structural limits on the loop’s shape. Cap recursion depth for sub-agents, and cap the total number of sub-agents spawned across a run — an agent that can spawn agents is a fork bomb waiting for a bad prompt. Cap retries per tool with exponential backoff, and after the cap return a terminal error the model cannot retry, rather than the same transient error again.

Good and bad.

Graceful degradation, which matters for user experience. When a limit is hit, do not just error out. Return the partial work with an explicit statement of what was completed and what was not, so the user gets something and can decide whether to continue. An agent that burned $4 and returns nothing is worse than one that burned $4 and returns three of five findings.

The observability that makes any of this tunable. Log every step: the action, arguments, result, token counts, cost, and latency. Trace the whole run with a correlation ID. Then plot the distribution of steps-to-completion for successful and failed runs — the gap between those two distributions is where your caps belong. Without this you are guessing.

The design principle underneath all of it. Bound at multiple independent levels, because each mechanism has a blind spot: a step cap does not stop one enormous tool result from blowing your context and cost; a token cap does not stop a fast loop of cheap calls from hammering an external API; loop detection does not catch an agent making steady, varied, useless progress. And put every limit in the harness, never in the prompt. A prompt is a request; a harness limit is a guarantee.

Follow-up: What about an agent that is making progress but far too slowly? → That is the hardest case, because no single check fires. Use a budget-aware prompt — tell the agent how much of its budget remains and instruct it to prioritize and summarize as it approaches the limit — plus a checkpoint at, say, 50% budget where the agent must report progress and either continue or hand back. Human-in-the-loop escalation at that checkpoint is the reliable answer for expensive long-running tasks.

Why the interviewer asks this. This is the question that reveals whether you have run an agent in production or only demoed one. Everyone who has shipped one has a story about a loop and a bill.

Saying it out loud. “Layered limits, all enforced in the harness and never in the prompt, because a prompt is a request and a harness limit is a guarantee. Max steps, wall-clock deadline, token budget, and a dollar cap — and I want two dollar caps, one per run and one rolling per-hour on the account, because a two-dollar per-run cap does nothing when a bug is spawning a thousand runs. Then loop detection, which is more useful than the step cap in practice: hash the tool plus normalized arguments and catch exact repeats, and when you catch one, tell the agent it’s repeating itself rather than just killing the run — it usually recovers. And I set the caps from the observed distribution of successful runs, not from a round number.”


Q144: What is the single biggest practical failure mode of agents in production?

In 30 seconds. “Compounding error over a long horizon. If each step is 95% reliable — better than most tool setups get — then twenty steps is 0.95 to the twentieth, about 36%, and fifty steps is under 8%. That’s why demos work and products don’t. And it’s worse than the arithmetic, because errors aren’t independent: one wrong belief lands in the context and poisons everything after it.”

The short version.

  • The failure: compounding error over a long horizon. Every other agent failure is a special case or much easier to fix.
  • Errors are absorbing, not independent — a corrupted premise poisons all downstream reasoning.
  • Recognizing failure is the weak capability: models do things better than they notice they did the wrong thing.
  • Context degrades over many steps: signal-to-noise falls, the original instruction recedes.
  • The fix is architectural, not a better model: shorten the horizon, verify externally, make failure loud.

Why it works.

The arithmetic that makes it vivid. If each step of an agent succeeds independently with probability , the probability of completing steps is . At — which is a genuinely good per-step reliability, better than most tool-calling setups achieve — the probability of a clean 20-step run is . At 50 steps, . A model that is right 95% of the time fails two out of three moderately long tasks.

This is why agent demos work and agent products do not. A demo is five steps: , and if you run it twice you get a good take. A real task is forty steps, and the same components deliver under 13%.

Why it is worse than the arithmetic suggests. Three effects make real agents fall below the independence bound.

Errors are not independent, they are absorbing. A wrong belief entering the context stays there, and every subsequent step conditions on it. An agent that misreads a config file does not merely have one bad step — it has a corrupted premise that poisons all remaining reasoning. The failures correlate, so the effective drops as the run proceeds.

Recovery requires recognizing failure, which is the weak capability. Models are noticeably better at doing things than at noticing they did the wrong thing. A tool that returns an empty result, a silently truncated file, or a plausible-but-wrong value often gets treated as success. The agent then builds confidently on sand. This is the specific gap that makes long-horizon autonomy hard.

Context degradation. Over many steps the context fills with tool outputs, errors, and retries. The signal-to-noise ratio falls, the original instruction recedes, and the model starts attending to the clutter — drifting from the goal, re-doing completed work, or losing a constraint stated at the top. Compaction helps and also loses information, so it trades one failure for another.

The visible symptoms, all downstream of this. Getting stuck in a repeated tool call. Confidently reporting success on a task that was not done — the worst one, because it is silent. Drifting from the original objective. Cost blowing up as the agent flails.

Good and bad.

What actually mitigates it. Not “a better model,” though that helps; the fixes are architectural.

Shorten the horizon. Decompose into subtasks that each complete in a handful of steps, with a verified checkpoint between them. Reliability then multiplies over the number of subtasks, not the number of steps, and each subtask starts with a clean context. This is the single highest-leverage change.

Verify at every step, mechanically. Do not trust the model to notice failure — run the tests, check the exit code, validate the schema, re-read the file you claimed to write. Ground truth from the environment beats the model’s self-assessment every time. A tool that returns a structured success/failure signal is worth far more than one that returns prose.

Make failure loud. Tools should error explicitly rather than return empty or truncated results that look like data. Half the silent-failure problem is tools that fail quietly.

Constrain the action space. Fewer, better-designed, more clearly-documented tools produce far higher per-step reliability than a large undifferentiated set. Every additional similar tool is another chance to pick the wrong one.

Checkpoint and make actions reversible. Snapshot state so a bad branch can be rolled back rather than repaired.

Keep a human at the irreversible steps. Full autonomy is the wrong target for most products; the achievable target is an agent that does 90% of the work and asks before it does anything it cannot undo.

Time-sensitive. The per-step reliability of frontier models is improving, which shifts the viable horizon outward year over year — the length of task an agent can complete unaided has been roughly doubling on a period of months by some measurements. The structure of the argument does not change: whatever is, still decays, so the engineering response is always to shorten and verify externally rather than to wait for .

Follow-up: Which is worse, an agent that fails loudly or one that gives up? → Giving up is far better. A loud failure is a retry; a confident wrong report is a wrong result that enters a downstream system and is discovered much later, by someone who trusted it. Design the reward and the prompt so that “I could not complete this, here is what I did and where I stopped” is an acceptable outcome, because otherwise you are training the agent to fabricate completion.

Why the interviewer asks this. Answering “hallucination” marks you as someone who has read about agents. Answering “compounding error over long horizons, here’s the math, and here’s why you fix it by shortening the horizon and verifying externally” marks you as someone who has shipped one.

Saying it out loud. “Compounding error over a long horizon. If each step is 95% reliable — which is honestly better than most tool setups get — then twenty steps is 0.95 to the twentieth, about 36%. Fifty steps is under 8%. That’s why demos work and products don’t. And it’s worse than that because errors aren’t independent: one wrong belief lands in the context and poisons everything after it, and models are much better at doing things than at noticing they did the wrong thing. So the fix isn’t a better model, it’s architecture — decompose into short subtasks with verified checkpoints, and verify with the environment rather than asking the model whether it succeeded.”