Scikit Learn Interview Questions — Practice Real Python ML Questions
Reviewed by Mark Dickie · Last updated
Scikit Learn is a Python machine learning library that provides consistent APIs for classification, regression, clustering, dimensionality reduction, and model evaluation. For an interview, you should know how the fit-predict-transform pattern works, when to use each cross-validation strategy, how to build and serialize pipelines, and how to pick the right metric for an imbalanced dataset. Interviewers also test whether you understand the bias-variance tradeoff in concrete terms, how regularisation strength maps to model behaviour, and what happens when data leaks through a preprocessing step that was fit on the full dataset before splitting.
What does a Scikit Learn interview typically test?
Most questions cluster around a handful of areas. The table below maps each area to what an interviewer is usually after.
| Topic | What gets tested | Common question types |
|---|---|---|
| Preprocessing & encoding | Choosing the right scaler or encoder for the data type | "When would you use StandardScaler vs MinMaxScaler vs RobustScaler?" |
| Model selection & cross-validation | Knowing when each CV strategy applies and its tradeoffs | "Why use StratifiedKFold instead of plain KFold?" |
| Pipelines & data leakage | Building pipelines that prevent leakage and stay reproducible | "What goes wrong if you fit the scaler before train_test_split?" |
| Evaluation metrics | Matching the metric to the problem and class balance | "Why is accuracy misleading on an imbalanced dataset?" |
| Algorithm internals | Core assumptions and hyperparameters of common estimators | "How does C affect an SVM, and what does gamma control?" |
| Ensemble methods | How bagging and boosting differ in practice | "What is the difference between RandomForest and GradientBoosting?" |
How should you prepare for Scikit Learn questions?
- Write out a full pipeline end-to-end on a small dataset: imputer, scaler, model, and cross-validated grid search. Being able to recite the code structure from memory saves time under pressure.
- Memorise which preprocessing step matches which data type — ordinal encoding for ordered categories, one-hot for nominal, target encoding only when you have enough data to avoid overfitting.
- Practice explaining regularisation in plain language. L1 shrinks coefficients to zero (feature selection); L2 shrinks them toward zero without zeroing them out.
- Run through the evaluation metrics so you can say, without hesitation, when to use F1, ROC-AUC, precision-recall AUC, and log loss.
- Learn the gotchas interviewers love: data leakage from fitting transformers on the full set, the difference between
fit_transformandtransform, and whyrandom_statematters for reproducibility.
The quiz below runs you through questions pulled from real interview formats. Use it to find gaps before the real thing.
Key facts
- Tarmac has 99 Scikit-learn interview questions on this topic, 25 of them on this page, at difficulty 1–5 of 5.
- Tarmac last reviewed these Scikit-learn interview questions on 31 August 2026.
At a glance
| Questions | 25 shown · 99 in the bank |
|---|---|
| Difficulty | 1–5 of 5 |
| Formats | Ordering, Short answer, Design exercise, Multiple choice, True / false, Flashcard, Multiple answer, Code output, Find the bug, Fill in the blank |
What you'll review
- estimator params
- random state reproducibility
- cross validation tools
- fit predict transform
- pipeline class
- data leakage sklearn
- categorical encoding
- hyperparameter search
- ensemble estimators
Practice questions
Try one before you open the answer. Pick an option and press Check; it's marked on the spot.
Scikit-learn/estimator-api/estimator-params
Order the steps in the standard scikit-learn estimator lifecycle, from earliest to latest, showing how hyperparameters and learned parameters flow through the API.#
Put these in order
Show answer
The scikit-learn estimator lifecycle proceeds: (1) instantiate the estimator via __init__ with hyperparameters, (2) call fit(X, y) to learn parameters stored in trailing-underscore attributes like coef_, then (3) call predict(X) or transform(X) to apply the learned parameters. You cannot skip or reorder these steps: predict before fit raises NotFittedError, and fit requires an existing estimator instance.
Scikit-learn estimators follow a strict three-phase lifecycle. First, the constructor __init__ receives and stores hyperparameters (like C, max_depth) — it does no learning. Second, fit(X, y) learns parameters from data and stores them in attributes with trailing underscores (like coef_, classes_). Third, predict or transform applies those learned parameters to new data. Calling predict before fit raises a NotFittedError, and fit cannot be called before the object exists, so this order is universal across all scikit-learn estimators.
Scikit-learn/common-pitfalls-sklearn/random-state-reproducibility
In scikit-learn, what is the purpose of passing the random_state parameter to an estimator or function such as train_test_split?#
Show answer
Setting random_state to a fixed integer ensures that the stochastic behavior of the estimator or function (e.g., shuffling, sampling, weight initialization) is reproducible across runs.
Scikit-learn uses random_state as a seed for its internal random number generators. When set to a fixed integer, the same sequence of pseudo-random numbers is produced every run, yielding identical results. When left at None, results may vary between runs.
Scikit-learn/model-selection-sklearn/cross-validation-tools
You are building a binary classification model with scikit-learn on a dataset of 10 000 rows where only 8% of samples belong to the positive class. You need to evaluate the model with cross-validation and also feed the same strategy into GridSearchCV for hyperparameter tuning. Design the cross-validation approach: which scikit-learn cross-validation tool(s) would you choose, what parameters would you set, and why is each choice important for this specific dataset?#
Show answer
For an imbalanced dataset with only 8% positives, I would use StratifiedKFold with n_splits=5, shuffle=True, and random_state=42. StratifiedKFold ensures that each fold preserves the original class ratio, so every fold contains roughly 800 positive samples out of 10 000; with plain KFold a fold could easily end up with very few or even zero positives, making that fold's evaluation meaningless or unstable. I set shuffle=True because the data may arrive sorted by class or time, and random_state=42 guarantees reproducibility across runs. With 10 000 rows, 5 folds gives 2 000 test samples per fold — enough for a stable estimate of the minority-class metrics. To use this inside GridSearchCV, I pass cv=StratifiedKFold(n_splits=5, shuffle=True, random_state=42) directly to the estimator's cv parameter; GridSearchCV will call split() on the provided splitter, so every hyperparameter candidate is evaluated with the same stratified folds. If I needed tighter confidence intervals on the scores, I could switch to RepeatedStratifiedKFold(n_splits=5, n_repeats=10, random_state=42), which repeats the stratified split with different shuffles and averages the results.
This design exercise tests whether a candidate can select the right scikit-learn cross-validation tool for an imbalanced dataset. The keyed criteria reward naming a stratified splitter, explaining why stratification matters for an 8% minority class, and specifying concrete, justified parameters — all of which the sample answer demonstrates.
Scikit-learn/model-selection-sklearn/cross-validation-tools
You are building a scikit-learn regression model on a dataset of daily sales figures collected over three years in chronological order. You need to evaluate the model with cross-validation without leaking future information into the training set. Design the cross-validation approach: which scikit-learn cross-validation tool would you choose, what parameters would you set, and why is each choice important for time-series data?#
Show answer
For a chronologically ordered daily sales series spanning three years (roughly 1 095 rows), I would use TimeSeriesSplit with n_splits=5. TimeSeriesSplit respects temporal order: each successive split adds more recent data to the training set and tests on the next block of future dates, so the model never sees tomorrow's sales while training on today's. This is fundamentally different from KFold or ShuffleSplit, which randomly assign rows to folds — in a time series that means future-dated observations would land in the training set, giving the model look-ahead information and inflating validation scores. With TimeSeriesSplit the evaluation is strictly forward-only: each test block is chronologically later than every row in its paired training set, meaning every test-fold index is strictly greater than every training-fold index within the same split, and the training window grows monotonically from fold to fold. I would leave max_train_size at its default (None, a growing window) because three years of daily data is modest and earlier history likely still carries useful seasonal signal; if the sales pattern shifted significantly over time (e.g., a business pivot), I would set max_train_size=365 to use a rolling one-year window instead. With n_splits=5, the splitter creates five train/test pairs where each test block is roughly 180 days, giving seasonal coverage across folds. To evaluate, I pass cv=TimeSeriesSplit(n_splits=5) into cross_val_score(model, X, y, cv=ts, scoring='neg_mean_squared_error'), and the splits are applied in chronological order automatically.
This design exercise tests whether a candidate can choose scikit-learn's TimeSeriesSplit for chronologically ordered regression data. The keyed criteria reward justifying the tool by contrasting it with named alternatives (c1), explaining the expanding-window mechanism with its strict index-ordering property (c2), and discussing concrete parameters (c3) — all demonstrated in the sample answer. The overlap in the original c2 trailing clause has been removed by replacing the comparative statement with a requirement to state the strict index-ordering property that distinguishes the mechanism itself.
Scikit-learn/estimator-api/fit-predict-transform
For a scikit-learn transformer like StandardScaler, what's the difference between calling .fit(X) and .transform(X)?#
Options
Show answer
.fit(X) computes and stores a scikit-learn transformer's parameters from X — for StandardScaler, each feature's mean and standard deviation — while .transform(X) applies an already-fitted transformation to data using those stored parameters, without recomputing them. This split is what lets a transformer be fitted once on training data and then correctly applied to held-out test or production data using training-set statistics, rather than statistics computed fresh from whatever data happens to be passed in.
This split is the foundation of scikit-learn's whole transformer API: .fit(X) learns whatever the transformer needs to learn from the data it's given — for StandardScaler, that's each feature's mean and standard deviation — and stores it as fitted attributes. .transform(X) then applies that already-learned transformation to data, which can be the same data or entirely different data (most importantly, held-out test data), using the parameters that were fitted, not recomputing new ones. .fit_transform(X) is a convenience method that does both in one call on the same data, but the two-step split is what makes it possible to fit once on training data and then correctly transform test data (or new production data) using training-set statistics rather than the test data's own. The other options invert or invent the relationship — fit doesn't transform anything on its own (c), the two methods do genuinely different things (b), and transform requires a prior fit to have parameters to apply, not the other way around (d).
Scikit-learn/common-pitfalls-sklearn/random-state-reproducibility
Setting random_state on a single scikit-learn estimator like RandomForestClassifier is enough to make an entire modeling script — including a preceding train_test_split call — fully reproducible across runs.#
Options
Show answer
False. random_state is a per-object parameter in scikit-learn, not a global seed, so fixing it on a RandomForestClassifier alone leaves other stochastic steps — like a preceding train_test_split call — unseeded and free to vary between runs. Genuine reproducibility requires explicitly setting random_state on every stochastic step in the script, including data splitting and cross-validation, not just the final estimator.
False. random_state is a per-object parameter, not a global setting — train_test_split, cross-validation splitters like KFold, and any stochastic estimator each have their own independent randomness that needs its own random_state fixed. Seeding only the RandomForestClassifier leaves train_test_split free to produce a different train/test partition on every run (since it has no seed of its own), so even though the forest's internal randomness is now fixed, the actual data it's trained and evaluated on still changes run to run. Genuine reproducibility means explicitly setting random_state on every stochastic step in the script, not just the model at the end of it.
Scikit-learn/pipelines-composition/pipeline-class
What does scikit-learn's Pipeline class do?#
Show answer
Chains a sequence of transformers (e.g. a scaler, an encoder) ending in a final estimator, so the whole sequence can be fit and used for prediction as a single object with the standard fit/predict interface. Crucially, when a Pipeline is passed to cross_val_score or GridSearchCV, every step gets refit fresh on only each fold's training data and applied to that fold's validation data purely via .transform() — which is what prevents validation-fold statistics from leaking into preprocessing. GridSearchCV can also tune hyperparameters across every step at once using the stepname__param naming convention, not just the final estimator.
The convenience of one fit/predict call is real, but the correctness guarantee under cross-validation — automatic per-fold refitting — is the reason Pipeline is considered standard practice rather than optional tidiness.
Scikit-learn/common-pitfalls-sklearn/data-leakage-sklearn
You are building a classification model with scikit-learn and a dataset of 10,000 rows. Which of the following preprocessing decisions, performed BEFORE calling train_test_split (or before cross-validation), would cause data leakage from the test folds into the training process? Select all that apply.#
Options
Pick every one that applies.
Show answer
Fitting StandardScaler on the full dataset before splitting, imputing with the global median computed before splitting, and fitting an OrdinalEncoder on all data before cross-validation all leak test-fold statistics into training. Dropping rows with a missing target before splitting does not leak, because those rows carry no usable supervised signal.
Options A, B, and C all compute statistics (mean/std, median, category mappings) using the full dataset before the split, so information from test-fold rows influences training-time preprocessing — the classic leakage pattern. Option D simply removes targetless rows that cannot be used for supervised learning at all; it does not transfer test-fold signal into training, so it does not cause leakage.
Scikit-learn/estimator-api/estimator-params
What does the following code print?#
from sklearn.linear_model import LogisticRegression
clf = LogisticRegression(C=1.0, solver='lbfgs')
clf.set_params(C=5.0)
print(clf.get_params()['C'])Show answer
5.0
set_params modifies the estimator in place and returns self. After clf.set_params(C=5.0), the C parameter is updated to 5.0. Calling get_params() returns a dictionary of all parameters, and get_params()['C'] retrieves the updated value, which is 5.0.
Scikit-learn/estimator-api/estimator-params
In scikit-learn's estimator API, what does a trailing underscore on an attribute name like model.coef_ conventionally indicate?#
Options
Show answer
It's an attribute that only exists after the estimator has been fitted — it stores something learned from the training data, as opposed to a hyperparameter passed to the constructor
scikit-learn's naming convention deliberately separates two kinds of state on an estimator: constructor parameters (LogisticRegression(C=1.0)) are hyperparameters the user chooses before fitting, while trailing-underscore attributes (.coef_, .intercept_, .feature_importances_, .classes_) are populated only once .fit() has run, holding whatever the estimator actually learned from the training data. Trying to access .coef_ before calling .fit() raises a NotFittedError precisely because it documents that dependency in the API itself — the trailing underscore is a visual signal that the attribute doesn't exist yet at construction time. It's neither a privacy convention (b — these are meant to be read directly, that's the whole point of exposing them), nor a deprecation marker (c — it's the opposite, a stable, deliberate naming pattern used throughout the library), nor classifier-specific (d — LinearRegression.coef_ is a standard regressor example of the exact same convention).
Scikit-learn/preprocessing-sklearn/categorical-encoding
For a nominal (unordered) categorical feature going into a linear model, which of these are genuine reasons to prefer OneHotEncoder over OrdinalEncoder? Select all that apply.#
Options
Pick every one that applies.
Show answer
For a nominal categorical feature going into a linear model, OneHotEncoder is preferred over OrdinalEncoder because ordinal encoding assigns arbitrary integer codes that a linear model interprets as a real numeric relationship between categories, while one-hot encoding gives each category its own independent binary column with no implied ordering. This concern is far less severe for tree-based models, since trees split on thresholds rather than assuming linear relationships between codes. OrdinalEncoder works fine inside a scikit-learn Pipeline, and one-hot encoding produces more output columns than ordinal encoding for the same feature, not fewer.
A linear model's coefficient multiplies a feature's numeric value directly, so if OrdinalEncoder maps 'red'→0, 'blue'→1, 'green'→2 with no real order behind those categories, the model is implicitly fit as if 'green' is numerically 'more' of whatever the coefficient represents than 'blue' is more than 'red' — a relationship the data never actually claimed (a). OneHotEncoder sidesteps this entirely by giving each category its own binary column, so no numeric ordering is ever implied between categories (b). The severity genuinely differs by model family, too: tree-based models split by asking 'is this feature ≤ some threshold,' so an arbitrary numeric code just needs a boundary between groups to exist somewhere, not any meaningful magnitude relationship, which is why ordinal encoding is a much smaller practical concern for trees/forests/boosted trees than for linear or distance-based models (e). OrdinalEncoder works perfectly well as a Pipeline step — it's a standard scikit-learn transformer with no such restriction (c is false) — and one-hot encoding a feature with N categories produces N (or N-1, with drop='first') columns versus ordinal's single column, meaning one-hot always produces more, not fewer, columns for the same feature (d is backwards).
Scikit-learn/common-pitfalls-sklearn/data-leakage-sklearn
Calling .fit_transform() on your training data, and then .transform() (not .fit_transform()) on your test data, is the correct pattern for avoiding leakage of test-set statistics into preprocessing.#
Options
Show answer
True. Calling .fit_transform() on training data learns and applies a transformation's parameters from data the model should legitimately learn from, while calling .transform() (not .fit_transform()) on test data applies that same already-learned transformation without recomputing anything from the test set — which is what keeps test-set statistics out of preprocessing. Calling .fit_transform() on the test set instead would compute new parameters from test data alone, evaluating the model against differently-scaled features than the ones it actually trained on.
True. .fit_transform() on the training set both learns the transformation's parameters from training data and applies them, which is correct since the training set is exactly the data those parameters should be learned from. Calling .transform() — not .fit_transform() — on the test set applies that same already-learned transformation without recomputing anything from the test data itself, which is exactly what keeps test-set statistics out of the preprocessing the model was effectively trained under. Calling .fit_transform() on the test set instead would compute a brand-new set of parameters from test data alone, meaning the model would be evaluated against features scaled differently than the ones it actually learned from — a leakage bug that also silently invalidates the evaluation.
Scikit-learn/estimator-api/fit-predict-transform
A model trained offline starts returning noticeably worse predictions once deployed, despite being the exact same trained model object. Which line is the bug?#
1| scaler = StandardScaler()
2| X_train_scaled = scaler.fit_transform(X_train)
3| model = LogisticRegression().fit(X_train_scaled, y_train)
4|
5| # ... later, at inference time, scoring a new batch of production requests ...
6| X_new_scaled = scaler.fit_transform(X_new)
7| prediction = model.predict(X_new_scaled)Options
Show answer
Line 6 — calling fit_transform on X_new recomputes a brand-new mean and standard deviation from whatever happens to be in that batch, instead of reusing the scaler already fitted on training data; the model was trained on features scaled against training-set statistics, so scoring against differently-scaled features silently produces wrong predictions
The scaler object at line 6 is the same object that was already fitted on X_train at line 2 — it's holding the training set's mean and standard deviation internally. Calling .fit_transform(X_new) on it throws that fitted state away and recomputes a fresh mean/standard deviation from whatever's in the new production batch, then scales X_new against those new-batch statistics instead of the training-set statistics the model actually learned its coefficients against. The model's weights encode relationships in the training-scaled feature space, so scoring features scaled a different way (especially if a production batch's distribution differs even slightly from training, e.g. it's smaller, or skewed toward one segment of users) produces predictions that are silently wrong, with no error raised anywhere. The fix is X_new_scaled = scaler.transform(X_new) — reusing the already-fitted scaler's stored parameters, never re-fitting at inference time. fit_transform on the training set (b) is exactly correct, since that's the data the scaler should legitimately learn from; the constructor-chaining syntax on line 3 (c) is completely valid, ordinary scikit-learn usage; and a fitted scaler is very much stateful — it holds the learned mean/std as fitted attributes, and calling fit_transform again explicitly discards and replaces that state rather than being a no-op (d is false).
Scikit-learn/pipelines-composition/pipeline-class
Explain what problem scikit-learn's Pipeline class actually solves, beyond just being a code-organization convenience.#
Show answer
A Pipeline chains a sequence of transformers and a final estimator into a single object that exposes the standard fit/predict interface, so the whole sequence can be treated as one unit. The convenience part is real — one .fit() call instead of manually chaining several — but the more important benefit is correctness under cross-validation and hyperparameter search: when you pass a Pipeline to cross_val_score or GridSearchCV, every preprocessing step (scaling, encoding, imputation) is refit from scratch on only each fold's training portion, and only ever applied via .transform() to that fold's validation portion. Preprocessing the entire dataset once, before cross-validation splits it, would let information from what becomes the validation fold leak into the fitted preprocessing parameters, inflating the reported score relative to real generalization. A Pipeline also lets GridSearchCV tune hyperparameters across every step at once (via the stepname__param naming convention), not just the final estimator, so a preprocessing choice like the number of PCA components can be searched jointly with the model's own hyperparameters.
The 'just convenience' framing is the trap this question is checking for. Pipeline's real value is correctness: it guarantees that inside cross-validation or GridSearchCV, every preprocessing step gets refit on only the current fold's training data and applied to validation data purely via .transform(), which is exactly what prevents the fold's own statistics from leaking into its own evaluation. A strong answer names both the convenience (one fit/predict call for a multi-step sequence) and the correctness guarantee (per-fold refitting inside CV/grid search), since the second is the part that actually matters for whether a reported score can be trusted.
Scikit-learn/model-selection-sklearn/cross-validation-tools
Order the steps of a leakage-safe scikit-learn modeling workflow, from raw data to a final, trustworthy performance number.#
Put these in order
Show answer
A leakage-safe scikit-learn workflow splits the raw data into train and test sets before any preprocessing happens, fits a Pipeline of preprocessing steps plus an estimator using only the training set, tunes hyperparameters via cross-validation confined to the training set, refits the selected pipeline on the entire training set with those chosen hyperparameters, and evaluates the final fitted pipeline exactly once against the held-out test set. Splitting first is what makes the held-out set genuinely unseen, and evaluating only once at the very end — with no tuning decisions ever influenced by it — is what keeps the final performance number an unbiased estimate rather than one the model was indirectly tuned against.
Each step exists to keep the test set genuinely unseen until the very end. Splitting first, before any preprocessing touches the data, is what makes 'held-out' actually mean something — if preprocessing ran on the full dataset before the split, test-set statistics would already have leaked into the transformers by the time evaluation happens. Fitting a Pipeline (rather than separate fit calls) on the training set alone ensures every preprocessing step's learned parameters come only from training data. Hyperparameter tuning via cross-validation happens next and must also stay confined to the training set, since the whole point of a held-out test set is that no tuning decision was ever influenced by it. Refitting the selected pipeline on the full training set (rather than shipping whichever fold happened to score best during tuning) uses all the training data available once the hyperparameters are settled. And evaluating against the test set comes strictly last, and exactly once — evaluating repeatedly and adjusting anything in response would turn the 'held-out' test set into something the model was effectively tuned against too, defeating its purpose as an unbiased final check.
Scikit-learn/common-pitfalls-sklearn/random-state-reproducibility
Flashcard (Front): You set random_state=42 on every estimator in your scikit-learn pipeline, yet your colleague running the same code on a different machine with the same scikit-learn version gets slightly different results. What is a likely root cause, and what does random_state actually guarantee?#
Show answer
Passing random_state=42 only guarantees reproducibility for that specific estimator on the same machine, same scikit-learn version, and same Python platform. Cross-version and cross-platform reproducibility is NOT guaranteed, because underlying algorithms (e.g., NumPy RNG, Cython code paths) can change between releases.
random_state pins the RNG seed for a single estimator's internal randomness, but reproducibility also depends on identical library versions, platform-level floating-point behavior, and thread ordering. It does not provide cross-version or cross-platform guarantees.
Scikit-learn/pipelines-composition/pipeline-class
When using cross_val_score (or GridSearchCV) with a model that needs feature scaling, why is it important to wrap the scaler and the model together in a Pipeline, rather than scaling the whole dataset once before running cross-validation?#
Options
Show answer
Fitting the scaler once on the entire dataset before splitting lets statistics from what becomes each fold's validation portion leak into what the model effectively trains on, inflating the cross-validation score relative to real generalization; a Pipeline refits the scaler on only each fold's training portion
The leakage here is subtle because the code runs without error either way — it's the score that's silently wrong. If you call scaler.fit_transform(X) on the full dataset before cross-validation splits it into folds, the mean and standard deviation used to scale every fold's data (including what will become that fold's validation portion) were computed using information from all the data, validation portion included. That means each fold's 'held-out' validation data isn't fully held out — its own statistics helped shape the scaling applied to it — so the reported cross-validation score is optimistically biased relative to what the model would actually do on truly unseen data. Wrapping the scaler and model together in a Pipeline and passing the pipeline to cross_val_score fixes this: for each fold, the pipeline's scaler is fit fresh on only that fold's training portion, and the validation portion only ever sees .transform() with those training-only statistics — genuinely never touched during fitting. This isn't a style preference (c) or a compatibility requirement between unrelated modules (d) — a bare scaler works fine as a cross_val_score step in isolation (b is false) — it's a correctness issue with a real, measurable effect on the score.
Scikit-learn/model-selection-sklearn/hyperparameter-search
Which of these are genuine risks or correct practices when using GridSearchCV for hyperparameter tuning? Select all that apply.#
Options
Pick every one that applies.
Show answer
Genuine risks with GridSearchCV include treating best_score_ as an unbiased estimate of performance on truly unseen data, when it's actually inflated by having been selected as the best performer across your specific folds — which is why a fully separate held-out test set is still needed for a less biased final estimate. Using plain KFold instead of StratifiedKFold on an imbalanced classification problem can leave some folds with almost no minority-class examples, making their metrics unreliable. The scoring parameter does genuinely change which hyperparameters get selected, and RandomizedSearchCV is frequently more compute-efficient than an exhaustive grid search over large parameter spaces, not less.
best_score_ is optimistically biased by construction — it's the score of whichever combination happened to perform best across your specific cross-validation folds, so some of that 'best' is genuine signal and some is the combination getting lucky on those particular folds (a); that's exactly why a fully held-out test set, untouched by anything inside the GridSearchCV call, is still needed afterward for a less biased final estimate (d). For imbalanced classification, plain KFold splits rows without regard to class balance, so a fold can end up by chance with almost no minority-class examples, making any metric computed on that fold noisy or meaningless — StratifiedKFold (the default GridSearchCV actually uses for classifiers) preserves each fold's class ratio to avoid exactly this (b). The scoring parameter genuinely drives which combination is selected as 'best' — passing scoring='f1' versus the default optimizes for a materially different objective, so option (c) describes the opposite of how it actually works. And RandomizedSearchCV is frequently more compute-efficient than an exhaustive grid search, especially over large or high-dimensional hyperparameter spaces, since it samples a fixed budget of combinations rather than evaluating every single one — grid search is not 'always' more efficient (e is false).
Scikit-learn/common-pitfalls-sklearn/data-leakage-sklearn
A model's offline evaluation looks great, but its accuracy on genuinely new data is noticeably worse. Which line introduces the leakage?#
1| from sklearn.model_selection import train_test_split
2| from sklearn.preprocessing import StandardScaler
3| from sklearn.linear_model import LogisticRegression
4|
5| scaler = StandardScaler()
6| X_scaled = scaler.fit_transform(X)
7| X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.2, random_state=42)
8| model = LogisticRegression().fit(X_train, y_train)Options
Show answer
Line 6 — fit_transform is called on the full dataset X before the train/test split, so the scaler's mean and standard deviation are computed using statistics from what will become the test set too, leaking test-set information into what the model is effectively trained on
The scaler is fit on X — the entire dataset — before train_test_split ever separates it into train and test portions, which means the mean and standard deviation used to scale every row (test rows included) were computed with the test rows' own values folded in. That's leakage: information about the test set (its actual values, via their contribution to the fitted mean/std) has influenced the preprocessing the model is trained under, so cross-validation or a naive train/test split evaluated this way reports an optimistic score relative to genuinely new data the scaler's statistics never touched. The correct order is to split first, then scaler.fit_transform(X_train) and scaler.transform(X_test) separately — or equivalently, wrap the scaler and model in a Pipeline and pass that to train_test_split/cross_val_score, which enforces this ordering automatically. random_state=42 (b) is exactly the right practice for reproducibility, not a bug; a missing random_state on LogisticRegression() (c) affects run-to-run reproducibility of that estimator's own internals, not leakage; and 'the scaler needs to see the full range of the data' (d) is the precise misconception this question tests — a scaler only ever needs to see the data it's legitimately allowed to learn from, which is the training set alone.
Scikit-learn/estimator-api/estimator-params
Consider the following scikit-learn code that manipulates a Pipeline's estimator parameters:#
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, MinMaxScaler
from sklearn.linear_model import LogisticRegression
pipe = Pipeline([
('scaler', StandardScaler()),
('clf', LogisticRegression(solver='liblinear'))
])
pipe.set_params(scaler=MinMaxScaler(feature_range=(-2, 2)))
pipe.set_params(scaler__feature_range=(0, 1), clf__C=7.0)
print(type(pipe.named_steps['scaler']).__name__)
print(pipe.named_steps['scaler'].feature_range)
print(pipe.named_steps['clf'].C)Show answer
MinMaxScaler
(0, 1)
7.0
The first set_params(scaler=MinMaxScaler(feature_range=(-2, 2))) replaces the entire scaler pipeline step with a fresh MinMaxScaler instance whose feature_range is (-2, 2). The second set_params call uses nested parameter notation: scaler__feature_range=(0, 1) updates the new MinMaxScaler's feature_range to (0, 1), and clf__C=7.0 updates the LogisticRegression's C to 7.0. Consequently: type(...).__name__ prints MinMaxScaler, the scaler's feature_range prints (0, 1), and the classifier's C prints 7.0.
Scikit-learn/estimator-api/fit-predict-transform
In the scikit-learn estimator API, hyperparameters are passed to __init__ and stored as attributes without modification. In contrast, any attribute that is estimated or learned during fit must be suffixed with a _____ in its name (e.g., coef_, classes_, n_iter_, components_). This convention allows get_params, clone, and pipeline machinery to distinguish learned state from user-specified configuration. For example, PCA stores n_components (set by the user) but components_ (learned during fit).#
Show answer
In the scikit-learn estimator API, hyperparameters are passed to __init__ and stored as attributes without modification. In contrast, any attribute that is estimated or learned during fit must be suffixed with a trailing underscore in its name (e.g., coef_, classes_, n_iter_, components_). This convention allows get_params, clone, and pipeline machinery to distinguish learned state from user-specified configuration. For example, PCA stores n_components (set by the user) but components_ (learned during fit).
The scikit-learn API convention mandates that every attribute learned during fit must end with a trailing underscore (_). This separates constructor-set hyperparameters (e.g., n_components, alpha) from estimated quantities (e.g., components_, coef_). The convention is relied upon by BaseEstimator.get_params (which inspects only __init__ parameters), clone (which creates a fresh estimator with the same hyperparameters but no learned state), and Pipeline internals.
Scikit-learn/estimator-api/fit-predict-transform
In the scikit-learn estimator API contract, the __init__ method must accept all hyperparameters as keyword arguments with defaults, store them as attributes unchanged, and must NOT perform any validation or transformation of those arguments. All parameter validation—such as checking that n_components is positive, that max_iter is an integer, or that kernel is a valid string—belongs in the _____ method instead. This rule ensures that clone(estimator) can reconstruct a pristine estimator with identical hyperparameters and no side effects, which is essential for GridSearchCV and cross-validation to work correctly.#
Show answer
In the scikit-learn estimator API contract, the __init__ method must accept all hyperparameters as keyword arguments with defaults, store them as attributes unchanged, and must NOT perform any validation or transformation of those arguments. All parameter validation—such as checking that n_components is positive, that max_iter is an integer, or that kernel is a valid string—belongs in the fit method instead. This rule ensures that clone(estimator) can reconstruct a pristine estimator with identical hyperparameters and no side effects, which is essential for GridSearchCV and cross-validation to work correctly.
The scikit-learn development guide explicitly states that __init__ should only store parameters and never validate or transform them; all validation belongs in fit. If validation were done in __init__, then clone—which calls __init__ with the parameters returned by get_params—could fail or produce side effects, breaking GridSearchCV, cross_val_score, and Pipeline. By deferring validation to fit, the framework guarantees that cloning and parameter-setting are side-effect-free operations.
Scikit-learn/supervised-estimators/ensemble-estimators
A RandomForestClassifier reports 98% cross-validated accuracy on a fraud dataset where only 0.5% of transactions are actually fraudulent, and the team is ready to ship it. Explain why accuracy alone is a misleading metric here, and what evaluation approach would actually reveal whether the model is useful.#
Show answer
With 0.5% fraud, a trivial model that predicts 'not fraud' for every single transaction would already score 99.5% accuracy without having learned anything at all — so 98% accuracy from the actual model is, on its face, worse than doing nothing, which is the first sign accuracy is the wrong lens here. Accuracy treats every misclassification equally, but on a problem this imbalanced, the overwhelming majority of the accuracy score is dominated by correctly predicting the easy majority class, and says almost nothing about how the model handles the tiny minority class that's actually the entire point of building it. The right evaluation starts with comparing against that trivial majority-class baseline to see if the model is doing anything at all, then moves to metrics that separate performance on the minority class specifically: precision (of transactions flagged as fraud, how many really are — matters for how many false alarms an investigations team has to chase) and recall (of transactions that really are fraud, how many the model actually catches — matters for how much fraud slips through), and F1 or a precision-recall curve if you need one number that balances both, since ROC-AUC can also look artificially strong on severe imbalance. A confusion matrix makes the actual false-positive/false-negative tradeoff visible directly, and the right operating threshold should be chosen based on the real cost of a missed fraud case versus the cost of a false alarm, not the default 0.5 probability cutoff.
This is a staff-level question because the trap is genuinely dangerous in practice: 98% sounds impressive in isolation, and only becomes obviously bad once compared to what a model that learned nothing would already score by exploiting the class imbalance. A strong answer explicitly computes or reasons about that majority-class baseline, names the actual metrics that separate minority-class performance from the accuracy number (precision, recall, F1, confusion matrix), and connects the choice back to the real-world cost asymmetry between a missed fraud case and a false alarm — which is ultimately what should drive both the metric choice and the decision threshold, not accuracy or even a single fixed probability cutoff.
Scikit-learn/common-pitfalls-sklearn/data-leakage-sklearn
You have a dataset X (features) and y (labels). You run SelectKBest(f_classif, k=10).fit_transform(X, y) on the full dataset to obtain a reduced feature matrix, then pass that reduced matrix to cross_val_score(LogisticRegression(), X_reduced, y, cv=5). This procedure introduces data leakage because the features selected were chosen using information from every fold's labels, including the held-out validation labels.#
Options
Show answer
True. Fitting SelectKBest on the entire dataset before cross-validation leaks information because the selected features are informed by labels in the held-out validation folds. The correct approach is to put feature selection inside a Pipeline so it is fit only on each training fold during CV.
SelectKBest with f_classif ranks features by their univariate correlation with the target y. When fit on the full dataset, every sample's label — including those that will later land in a validation fold — influences which 10 features survive. The classifier is then evaluated on folds whose labels already shaped its input, inflating cross-validated scores. The fix is to place SelectKBest inside a sklearn.pipeline.Pipeline so that feature selection is fit only on each training fold during cross-validation. Note that cross_val_score itself has no way to detect or undo leakage that happened before it was called; it simply receives the already-reduced matrix.
Scikit-learn/estimator-api/estimator-params
The custom estimator below passes basic smoke tests — fit and transform produce correct results. However it breaks when used inside GridSearchCV or when clone() / get_params() is called. Identify the single buggy line.#
from sklearn.base import BaseEstimator, TransformerMixin
import numpy as np
class FeatureSelector(BaseEstimator, TransformerMixin):
def __init__(self, n_features=10):
self.k = n_features
def fit(self, X, y=None):
self.indices_ = np.argsort(X.var(axis=0))[-self.k:]
return self
def transform(self, X):
return X[:, self.indices_]Show answer
The bug is on line 6.
Scikit-learn's BaseEstimator.get_params() introspects the __init__ signature via inspect.signature to discover parameter names, then retrieves each value with getattr(self, name). The constructor parameter is n_features, but line 6 stores it as self.k. Calling get_params() raises AttributeError because getattr(self, 'n_features') fails. This breaks clone(), set_params(), GridSearchCV, and any tooling that relies on standard estimator introspection. The fix is self.n_features = n_features.
Related interview questions
The other 74 questions
This page shows 25 and marks what you pick. That's as far as a page can go. A free account opens the other 74 and keeps every answer. What you miss comes back until it's right: after a day, then at longer gaps.
Free · the whole bank · 100 marked answers per 30 days · written feedback on the paid plan