Classical Machine Learning Algorithms Questions
Foundational non-deep-learning models and when to reach for each. Covers linear and logistic regression, decision trees and ensemble methods (random forests, gradient boosting), support vector machines, k-nearest neighbors, and clustering, including their assumptions, strengths, and failure modes. Focuses on algorithm selection and the numerical and implementation considerations behind these workhorse models.
A decision tree on a fraud dataset fits the training data almost perfectly but loses a lot of accuracy on validation. Which tree-growth settings would you look at first, and how does each one change the tree's behavior?
Sample Answer
Direct answer
A near-perfect training fit with a much worse validation score on a tree is the textbook overfitting signature, and the first place to look is whatever is currently letting the tree grow arbitrarily specific: max_depth, min_samples_leaf, min_samples_split, and cost-complexity pruning (ccp_alpha). Each one attacks the same problem from a different angle, and the fix is usually two or three of them together, not a single knob.
Structured elaboration
| Setting | What it controls | How it changes tree behavior |
|---|---|---|
max_depth | Maximum number of splits from root to leaf | Directly caps how many conditions can be chained together to isolate a tiny subset of training rows; the single blunt-but-effective lever |
min_samples_leaf | Minimum training examples allowed in any leaf | Blocks splits that would create leaves memorizing a handful of points; larger values make the tree smoother and less brittle |
min_samples_split | Minimum examples needed before a node is even considered for splitting | A pre-filter that stops the search from touching already-small, likely-noisy nodes |
max_leaf_nodes | Total number of final leaves allowed, tree-wide | Caps overall model complexity directly rather than depth or per-node size |
ccp_alpha (cost-complexity pruning) | Post-hoc penalty per leaf, grown fully then pruned back | Removes branches whose validation-relevant gain doesn't justify their added complexity, complementary to depth/leaf constraints rather than a replacement for them |
Why fraud data specifically makes this worse. Fraud datasets are typically severely class-imbalanced and full of near-duplicate legitimate transactions with a handful of true positives scattered among them. An unconstrained tree will happily carve out single-leaf rules that isolate individual fraud examples by coincidental feature combinations, rules that generalize to nothing. This is exactly the setting where min_samples_leaf matters more than usual: a leaf of size 1 on a rare-event dataset is almost never a real pattern.
My usual order: constrain leaf size and depth first (they're cheap to reason about and directly address "the tree is memorizing individual rows"), then add ccp_alpha if the tree still looks overly complex after that, since pruning is more expensive to tune (it requires its own validation curve) and works best as a refinement on top of already-sane depth/leaf settings rather than as the sole fix.
Worked example
import numpy as np
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score
from sklearn.datasets import make_classification
X, y = make_classification(n_samples=4000, n_features=15, n_informative=6,
weights=[0.97, 0.03], flip_y=0.02, random_state=12)
Xtr, Xval, ytr, yval = train_test_split(X, y, test_size=0.3, stratify=y, random_state=0)
results = []
for depth, min_leaf in [(None, 1), (6, 1), (6, 20), (4, 50)]:
t = DecisionTreeClassifier(max_depth=depth, min_samples_leaf=min_leaf, random_state=0)
t.fit(Xtr, ytr)
train_auc = roc_auc_score(ytr, t.predict_proba(Xtr)[:, 1])
val_auc = roc_auc_score(yval, t.predict_proba(Xval)[:, 1])
results.append((depth, min_leaf, train_auc, val_auc))
Running this on a synthetic imbalanced dataset (4,000 samples, roughly 3% positive rate, similar shape to a fraud problem) with a 70/30 train/validation split gives (verified):
max_depth | min_samples_leaf | Train AUC | Validation AUC |
|---|---|---|---|
| unconstrained | 1 | 1.000 | 0.681 |
| 6 | 1 | 0.829 | 0.769 |
| 6 | 20 | 0.881 | 0.803 |
| 4 | 50 | 0.779 | 0.747 |
The unconstrained tree hits a perfect train AUC of 1.000 (it has memorized the training set) but only 0.681 on validation, a large gap that is the overfitting signature described in the question. Constraining max_depth=6 and min_samples_leaf=20 together brings training AUC down to 0.881, but validation AUC actually goes up to 0.803: the model got measurably better at generalizing by giving up some of its training-set fit. That's the concrete evidence that these settings aren't just cosmetically closing the train/validation gap, they're producing a genuinely better model. Pushing further to max_depth=4, min_samples_leaf=50 overshot into mild underfitting here (validation AUC dropped back to 0.747), which is the reason this is a search, not a single guess.
Trade-offs & pitfalls
- Don't stop at "the gap closed"; confirm validation performance actually improved, not just that train and validation scores got closer together (a tree that's bad on both would also have a small gap).
min_samples_leafandmax_depthinteract: setting a large minimum leaf size implicitly limits how deep the tree can meaningfully grow anyway, so tune them jointly rather than treating each grid dimension as fully independent.- Cost-complexity pruning needs its own validation curve (grow the full tree, then sweep
ccp_alphaand measure held-out performance at each pruning level); skipping this and picking an arbitraryccp_alphavalue is a common shortcut that under- or over-prunes. - On severely imbalanced data like fraud, growth-control hyperparameters interact with your resampling/class-weighting strategy: an aggressively subsampled majority class changes what "min_samples_leaf=20" effectively means relative to the minority class, so re-tune growth controls whenever the resampling strategy changes.
You're comparing two models on cross-validation: Model A scores 0.78 with a std of 0.01, Model B scores 0.80 with a std of 0.07. How do you interpret that, and which would you pick for production?
Sample Answer
Direct answer
A 0.02 mean-score edge for Model B is not meaningful once you account for its much larger fold-to-fold variability: its performance swings enough that the true difference between A and B could easily be zero, or could even favor A on a bad day. For a production decision, I would lean toward Model A unless I can explain and control the source of Model B's instability.
Structured elaboration
Read the numbers as a distribution, not a point estimate. A cross-validation score is itself a random variable, since it depends on which folds happened to land where. A mean of 0.78 with std 0.01 says "consistently around 0.78 no matter how the data is split." A mean of 0.80 with std 0.07 says "somewhere between roughly 0.7 and 0.9 depending on the split," a seven-fold larger spread. The higher mean could reflect a genuinely better model, or it could reflect one or two folds where Model B got lucky (e.g., an easy subset of data, or a fold correlated with a spurious feature it latched onto).
Turn the spread into an interval. With k folds, the standard error of the mean CV score is approximately SE=std/k (treating fold scores as roughly independent, which is an approximation since folds share the same training data, but a standard one for a sanity check). Assuming the common default of k=5 folds:
SEASEB=0.01/5≈0.0045,95% CIA≈0.78±2(0.0045)=[0.771, 0.789]=0.07/5≈0.0313,95% CIB≈0.80±2(0.0313)=[0.737, 0.863]Model A's interval sits almost entirely inside Model B's, so the two are statistically indistinguishable on this evidence; the 0.02 gap is well within B's own noise band.
What drives high variance, and what to check. Common causes: B is more sensitive to which examples land in train vs. validation (small effective sample size per fold, or a few high-leverage outliers), B is overfitting on some folds (deeper trees, less regularization), or there is a subtle leakage or preprocessing bug that helps on some folds and not others. Before trusting B, I would inspect the per-fold scores directly rather than just the summary statistics, and check whether the bad folds correlate with a specific data slice (time period, customer segment, source).
Worked example
See the interval computation above, it is the worked example: given the assumed k=5, A's approximate 95% CI is [0.771,0.789] and B's is [0.737,0.863]. The intersection of the two intervals is [0.771,0.789], about 1.8 percentage points wide, and it sits entirely inside B's interval, versus a raw mean gap of only 2 points. That overlap is the concrete reason to not treat B as the winner on mean score alone.
Trade-offs & pitfalls
- Production risk is not just about the expected metric, it is about the worst realistic case. A model whose CV std is 7x another's is more likely to have a bad month on live traffic that drifts even slightly from the training distribution.
- Don't default to A blindly either: if B's instability is diagnosed and traceable to a fixable cause (e.g., one noisy fold from a known bad data batch), fixing that cause could make B the better choice with a tightened std.
- A paired comparison (same folds, same splits, difference-per-fold) is more powerful than comparing two independently-summarized mean/std pairs, since it removes fold-to-fold difficulty variation common to both models; if available, prefer that over the interval-overlap heuristic used above.
- Calibration matters as much as discrimination for production: a model with unstable CV performance often also has unstable predicted probabilities, which matters if downstream thresholds or business rules depend on calibrated scores.
You have a dataset with thousands of numeric features, many of them strongly correlated, and far fewer samples than you would like. Would you use PCA here? Walk through how you would decide, and what you gain and lose by applying it before modeling.
Sample Answer
Direct answer
Yes, PCA is a reasonable default here: strong pairwise correlation among thousands of features is exactly the regime where a handful of components can absorb most of the variance, and reducing dimensionality before modeling helps when samples are scarce relative to features, because it shrinks the effective number of parameters the downstream model has to estimate. The decision is not PCA-or-nothing, though: it trades interpretability and some potentially label-relevant signal for variance compression, dimensionality reduction, and (indirectly) variance reduction in whatever model comes after it, so it is worth validating against alternatives rather than reaching for it reflexively.
Structured elaboration
Why correlated features plus scarce samples favors PCA. With n samples and p features where p≫n, the covariance matrix is rank-deficient and any model with p free parameters is in a severe small-sample regime, prone to high variance and overfitting. There is also a hard structural fact worth knowing: after centering, the n centered rows of X sum to zero, which forces them to lie in an (n−1)-dimensional subspace regardless of how large p is. So no matter how many thousands of features you have, PCA can produce at most n−1 components with nonzero variance; strong correlation among the original features is what determines whether most of that variance concentrates in a handful of components rather than being spread thinly across all n−1.
Decision checklist:
- Check the correlation structure first, either a correlation heatmap or the eigenvalue spectrum of the covariance matrix (the sorted list of eigenvalues, each one representing how much variance the corresponding principal component captures). If eigenvalues drop off sharply after the first few, PCA will compress well; if they decay slowly, PCA buys little.
- Decide whether the downstream model is linear or tree-based. Linear/distance-based models (linear/logistic regression, k-NN, SVM) tend to benefit most from PCA's decorrelation and dimensionality reduction; tree-based models (random forests, gradient boosting) usually do fine on raw correlated features and may lose useful axis-aligned splits after rotation.
- Decide whether you need interpretability. If a stakeholder needs to know "which original features drove this prediction," PCA's linear-combination components make that harder to answer directly (the loadings, the per-original-feature weights that make up each component, tell you which original features contribute most to a given component, but that's still an indirect read compared to a model built directly on the original features).
- Always fit the PCA transform (mean and components) on the training fold only, then apply it to validation/test, to avoid leaking information about the held-out set into the projection.
- Treat the number of components as a hyperparameter tuned by cross-validated downstream performance, not just a fixed variance threshold, when a supervised task is the actual goal.
What you gain: fewer, decorrelated features for the downstream model to fit, which reduces variance and the risk of overfitting when n is small; faster training and smaller storage; and, as a side effect, PCA acts as a form of denoising if the discarded low-variance directions are mostly noise.
What you lose: direct interpretability of individual input features; any predictive signal that happens to sit in a low-variance direction, since PCA is unsupervised and has no way to know a direction is small-variance-but-label-relevant; and a small amount of information whenever you keep k<n−1 components (the reconstruction error grows as you discard more).
Worked example
A small, fully reproducible illustration of the concentration effect. Simulate data from 3 shared latent factors plus feature-specific noise, with n=50 samples, p=200 features, a fixed seed, so anyone re-running this gets the same numbers:
import numpy as np
rng = np.random.default_rng(42)
n, p, k_true = 50, 200, 3
latent = rng.normal(size=(n, k_true))
loadings = rng.normal(size=(k_true, p))
X = latent @ loadings + rng.normal(scale=0.5, size=(n, p))
Xc = X - X.mean(axis=0)
_, S, _ = np.linalg.svd(Xc, full_matrices=False)
explained = (S ** 2) / (S ** 2).sum()
print("rank of Xc:", np.linalg.matrix_rank(Xc)) # 49, capped at n-1
print("explained variance, top 3:", explained[:3].round(4))
print("cumulative, top 3:", explained[:3].sum().round(4))
Running this gives explained variance ratios of approximately [0.4239, 0.3201, 0.1646] for the first three components, summing to about 0.9086, and a matrix rank of 49 (which is exactly n−1, confirming the structural cap above). Despite 200 features, three components recover over 90% of the variance because the features were generated from only 3 latent factors, which is the same mechanism that makes real strongly-correlated feature sets compress well.
Trade-offs & pitfalls
- Fitting PCA on the full dataset (train + test) before splitting is a common leakage mistake; always fit on train only.
- Standardize before PCA if the thousands of features are not on comparable scales, or the components will be dominated by whichever features happen to have the largest raw variance.
- Don't treat a variance-based cutoff as the final answer when there is a supervised target; cross-validate the number of components against the actual downstream metric, since maximum-variance directions and maximum-label-information directions are not guaranteed to coincide.
- If interpretability or nonlinear structure genuinely matters more than compression, compare PCA against supervised feature selection (L1-regularized models, mutual information) or a nonlinear reduction, rather than assuming PCA is the only lever available.
Decision trees can't extrapolate beyond the range of values seen in training. Where does that bite you in practice, and how might you combine a linear model with a tree ensemble to get the best of both?
Sample Answer
Direct answer
Because a tree predicts by falling into a leaf and returning that leaf's training-data statistic, any input beyond the range of values the tree saw during training lands in the same boundary leaf as the most extreme training examples and gets the same flat prediction, no matter how far out it goes. This bites hardest in trending or growing quantities (time, price, size, demand) where production data routinely drifts past the training range. A common fix is a hybrid: a linear (or otherwise extrapolation-aware) component captures the global trend, and a tree ensemble models the nonlinear residual structure on top of it.
Structured elaboration
Where it bites in practice.
- Time-indexed or growth features: a model trained on 2023-2024 sales data has never seen "days since launch = 800"; if the true relationship keeps growing, the tree flatlines at the value of its last training bucket.
- Monotonic business quantities: square footage, years of experience, account age, order volume; any of these can exceed the training range as the business scales, and the tree silently under- or over-predicts by defaulting to boundary-leaf behavior instead of failing loudly.
- Distribution shift in production: even without a true trend, if the training sample happened not to cover the full feature range (a sampling gap, not real extrapolation), a tree behaves identically: flat prediction at the edge, with no signal that it's operating out of its comfort zone.
Why trees behave this way. A regression tree's prediction is the mean of the training targets in the leaf a query point falls into; splits are chosen only within the observed range of each feature. There is no notion of "beyond the last split point, keep going in the same direction," because a tree has no parametric form to project outward with. Gradient boosting inherits the same property leaf by leaf, even though the ensemble as a whole can produce a step-like approximation.
Hybrid linear + tree design. Model the target as y≈g(x)+h(x), where g is a simple linear (or otherwise monotonic, extrapolation-safe) term capturing the global trend, and h is a tree ensemble capturing nonlinear interactions and local structure. A practical two-stage recipe:
- Fit g (e.g., ordinary least squares, or a linear model with monotonic constraints on the trending feature) to the full training data.
- Compute residuals ri=yi−g(xi).
- Fit a tree ensemble to the residuals r, using the remaining features (interactions, nonlinearities the linear term misses).
- At inference, predict y^(x)=g(x)+h(x); the linear term keeps extrapolating sensibly outside the training range, while the tree term contributes whatever local correction it learned, which naturally saturates near its own training boundary rather than dominating out-of-range behavior.
An alternative that avoids a strict two-stage pipeline: feed the linear model's prediction in as an additional feature to the tree ensemble, letting the trees learn corrections on top of it; this is more flexible but requires care that the ensemble doesn't simply relearn (and then flatten) the linear signal itself.
Worked example
Take a toy pricing relationship price=100,000+200×sqft, and suppose training data covers sqft in [500,2800]. At the training boundary, price(2800)=100,000+200×2800=660,000; a pure tree's boundary leaf predicts this same 660,000 value for any query at or beyond 2800 sqft, since that leaf's training examples top out there. Query a 5000 sqft property: the tree still outputs 660,000, a flat prediction, while the true (and the linear component's) value is 100,000+200×5000=1,100,000, an underprediction of 1,100,000−660,000=440,000, exactly the extrapolation gap the linear term is meant to close. In the hybrid, the linear term alone accounts for the full trend at 5000 sqft, and the tree-fitted residual term only needs to add whatever local nonlinear correction it learned near the edge of its own training range, not carry the entire extrapolation.
Trade-offs & pitfalls
- The hybrid only helps if the linear term's functional form is roughly right; if the true trend is itself nonlinear beyond the training range, a plain linear extrapolation can be just as wrong as the tree's flat one, only in a different direction.
- Fit the two stages with the same cross-validation discipline as any two-stage pipeline: compute residuals only from a model trained on the current fold's training data, never on data that includes the validation or test fold, or the residual-fitting step leaks information.
- Monotonic constraints (available natively in XGBoost and LightGBM) are a lighter-weight alternative to a full linear+tree hybrid when you only need the tree ensemble itself to keep moving in a known direction past the training range, without a separate linear model.
- In production, the real defense is detection, not just architecture: log when incoming features exceed the training range and flag those predictions as extrapolated, since even a hybrid model's confidence in that regime is lower than the metrics computed on in-range validation data suggest.
What's cross-validation, and why do you use it? How does k-fold differ from stratified k-fold, and what goes wrong if you use plain random k-fold on time-dependent data?
Sample Answer
Direct answer
Cross-validation estimates how a model will perform on unseen data by repeatedly holding out different slices of the training set, fitting on the rest, and averaging the validation scores, which gives a much more reliable estimate than a single train/test split. Stratified k-fold is the same idea but forces each fold to preserve the overall class proportions, which matters for imbalanced classification. Plain random k-fold on time-dependent data is actively wrong: it can put future observations in the training fold and past observations in the validation fold, letting the model implicitly learn from the future and inflating your validation scores in a way that won't hold up in production.
k-fold vs. stratified k-fold
k-fold: split the data into k roughly equal, randomly-assigned folds. Train on k−1 folds, validate on the remaining one, repeat k times so every fold serves as validation exactly once, then average the k scores. This gives both a mean performance estimate and a sense of variance across folds.
Stratified k-fold: the same procedure, but folds are constructed so each one has (approximately) the same class distribution as the full dataset. Plain random k-fold does not guarantee this, and on an imbalanced dataset a small, unlucky fold can end up with very few or zero positive examples, making that fold's precision/recall undefined or wildly noisy.
Worked example: why stratification matters, with an explicit fold assignment
Take 10 labeled examples with an 8:2 class split, where the positives happen to be examples 9 and 10 (a realistic scenario: data is often collected or sorted in a way that clusters similar rows, e.g. by signup date or by a batch ID):
labels=[0,0,0,0,0,0,0,0,1,1]A naive unshuffled 5-fold split done as contiguous blocks of 2 (fold 1 = examples 1-2, fold 2 = examples 3-4, ..., fold 5 = examples 9-10) puts both positives in fold 5 and zero positives in every other fold. Folds 1 through 4 can't report a meaningful precision or recall (no positives to find), and fold 5's score is based on a validation set that's 100% positive, an unrepresentative extreme in both directions. Stratified 5-fold instead constructs each fold to contain roughly 0.2 positives per fold on average (in practice, one fold gets one positive, the rest are handled by scikit-learn's allocation rule so the 2 positives are spread as evenly as the count allows), so every fold's score is at least computable and representative of the true 20% rate.
Time-dependent data: what goes wrong with random k-fold
If rows are ordered in time (say, January through December), random k-fold shuffles them across folds without regard to date. A given fold's training set can then contain December data while its validation set contains June data, meaning the model is evaluated on "predicting the past" using information from "the future" that would never have been available at prediction time in production. This inflates validation metrics, sometimes substantially, because the model can pick up on trends, seasonality, or even data-leakage artifacts (like a feature that encodes a later processing step) that won't be available at actual inference time.
The fix is a time-aware split: forward-chaining (expanding-window) or rolling-window cross-validation, where every validation fold's data is strictly later in time than everything in its training fold, mirroring how the model will actually be used.
Trade-offs and pitfalls
- Larger k (more folds) means each fold is smaller and more training data is used each round (lower bias in the estimate), but the folds are more correlated with each other and it costs more compute; k=5 or k=10 is the usual default trade-off, and k=n (leave-one-out) is only practical for very small datasets.
- Stratification fixes class-proportion imbalance across folds but does nothing for other kinds of dependence, like multiple rows belonging to the same user; that needs
GroupKFoldso a given user's rows never span both the training and validation fold. - Time-aware CV and stratified CV solve different problems and aren't automatically compatible; if you have a time-dependent, imbalanced classification problem, you generally need a custom split that respects both the time ordering and per-fold class representation.
- Cross-validation gives you an estimate of generalization error for model selection, but the final reported number should still come from a held-out test set that was never touched during CV, since repeatedly tuning against CV folds can itself overfit to those folds.
Unlock Full Question Bank
Get access to all Classical Machine Learning Algorithms interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.