Technical Interview Questions¶
These are the questions that appear most frequently across data science interviews at companies that hire junior-to-mid level candidates. For each question: the model answer is in the collapsible block. Read the question, write or say your answer out loud, then check.
Do not memorise these answers word-for-word. Understand the reasoning behind each one. An interviewer will follow up, and a memorised answer collapses the moment they push.
Statistics and Probability¶
Q1: What is a p-value? Explain it without using the word "probability of the null hypothesis being true."
Show answer
A p-value is the probability of observing a result at least as extreme as the one you got, assuming the null hypothesis is true. It measures how surprising your data is under the null model.
Common misconception: a p-value of 0.03 does not mean there is a 3% chance the null hypothesis is true. It means: if the null were true, there would be only a 3% chance of seeing data this extreme. The null hypothesis is either true or it is not — it does not have a probability.
In practice: p < 0.05 is a convention, not a law. It was chosen by Fisher and has stuck around despite decades of criticism. A p-value alone tells you nothing about effect size, practical significance, or whether your experiment was properly designed.
Q2: What is a confidence interval? What does "95% confident" actually mean?
Show answer
A 95% confidence interval means: if you repeated this experiment 100 times under identical conditions and computed the interval each time, approximately 95 of those intervals would contain the true population parameter.
It does not mean: "there is a 95% chance the true value falls in this interval." The true value is fixed — it either is or is not in any specific interval you compute. The probability statement applies to the procedure, not to the interval you have in hand.
Practically: a wide confidence interval means your estimate is imprecise, usually because your sample is too small. A narrow interval means you have enough data for a reliable estimate.
Q3: You run an A/B test. The result is statistically significant (p = 0.04). Should you ship the change?
Show answer
Not necessarily. Statistical significance is only one criterion.
Also check: - Effect size: is the lift practically meaningful? A 0.1% improvement in click-through rate on a low-value page may not justify the engineering cost. - Sample size and duration: did the test run long enough to capture weekly seasonality? Did it reach the planned sample size? - Novelty effect: did users engage more because the change was new, not because it is better? - Segment consistency: does the improvement hold across user segments, or is it driven by one group? - Secondary metrics: did any guardrail metrics (latency, revenue per session, churn) move negatively?
A result of p = 0.04 with a meaningless effect size and a test that ran for two days is not a good reason to ship.
Q4: What is the difference between Type I and Type II error? Which is worse?
Show answer
- Type I error (false positive): you reject the null hypothesis when it is actually true. You conclude there is an effect when there is none. Rate controlled by alpha (typically 0.05).
- Type II error (false negative): you fail to reject the null hypothesis when there actually is an effect. You miss a real signal. Rate is beta; power = 1 - beta.
Which is worse depends entirely on the problem: - In medical testing, a false negative (missing a disease) is often worse than a false positive (unnecessary follow-up). - In fraud detection, the answer depends on the cost of false accusations vs. the cost of missed fraud. - In A/B testing, shipping a bad change (Type I) is usually worse than missing a good one (Type II).
There is no universal answer. The correct answer is: "it depends on the asymmetry of the error costs."
Machine Learning Concepts¶
Q5: What is the bias-variance tradeoff?
Show answer
Every model's prediction error can be decomposed into three sources:
- Bias: error from incorrect assumptions in the model. A high-bias model is too simple — it underfits and misses structure in the data.
- Variance: error from sensitivity to fluctuations in the training set. A high-variance model is too complex — it fits noise and does not generalise.
- Irreducible error: noise inherent in the problem that no model can eliminate.
The tradeoff: reducing bias usually increases variance and vice versa. A linear model has high bias (strong assumptions) but low variance. A deep decision tree has low bias but high variance.
Practical implication: your goal is to find the model complexity where both bias and variance are acceptably low — not to minimise one at the expense of the other. Cross-validation is how you measure this on real data.
Q6: What is regularisation? Why does it help?
Show answer
Regularisation adds a penalty term to the loss function that grows with the magnitude of the model's parameters. This discourages the model from fitting noise by assigning large weights to any single feature.
Two common forms: - L1 (Lasso): penalty is the sum of absolute values of coefficients. Drives some coefficients to exactly zero — performs feature selection as a side effect. - L2 (Ridge): penalty is the sum of squared coefficients. Shrinks all coefficients toward zero but rarely to exactly zero. Better when many features are weakly predictive. - ElasticNet: combines L1 and L2.
Regularisation helps when your model is overfitting — when it performs well on training data but poorly on validation data. The hyperparameter controlling strength (lambda or alpha) is tuned via cross-validation.
Q7: How does a random forest work? Why is it often better than a single decision tree?
Show answer
A random forest is an ensemble of decision trees. It builds many trees, each on a bootstrap sample of the training data (sampling with replacement). At each split in each tree, only a random subset of features is considered.
Prediction: for regression, average the outputs of all trees. For classification, take the majority vote.
Why it outperforms a single tree: - Variance reduction: a single deep tree overfits. When you average many trees that each overfit slightly differently, the errors cancel out and the ensemble is more stable. - Decorrelation: by restricting features at each split, the trees are less correlated with each other — which is the condition under which averaging actually reduces variance. - Bias is preserved: each tree is grown deep (low bias). The ensemble keeps the low bias while reducing variance.
Hyperparameters to tune: n_estimators (more trees = more stable, diminishing returns), max_features (default is sqrt(n_features) for classification), max_depth or min_samples_leaf to control tree depth.
Q8: What is gradient boosting and how does it differ from random forest?
Show answer
Gradient boosting also builds an ensemble of trees, but sequentially rather than in parallel. Each tree is trained to predict the residual errors of the previous ensemble.
- Random forest: trees built independently in parallel; averaging reduces variance.
- Gradient boosting: trees built sequentially; each one corrects where the previous was wrong. Reduces bias more aggressively.
Gradient boosting often achieves better accuracy than random forests but is more sensitive to hyperparameters and more prone to overfitting if not regularised. Common implementations: XGBoost, LightGBM, CatBoost.
Hyperparameters that matter most: n_estimators, learning_rate (smaller = more trees needed), max_depth (keep shallow, typically 3–6), subsample, colsample_bytree.
Q9: When would you use SVM over a tree-based model?
Show answer
SVMs work well when: - The feature space is high-dimensional (e.g., text classification with TF-IDF features) - There is a clear margin of separation between classes - You have a moderate dataset size (SVMs scale poorly to millions of rows) - The kernel trick lets you find non-linear boundaries efficiently
Tree-based models (Random Forest, XGBoost) are usually preferable when: - You have tabular data with mixed feature types - You need interpretability (feature importances) - Your dataset is large - You need probability estimates (SVMs require calibration)
In practice, for most tabular data science problems, gradient boosted trees outperform SVMs. SVMs remain strong for text and image tasks where the feature space is very high-dimensional and sparse.
Q10: What is cross-validation and why do you use it instead of a single train/test split?
Show answer
Cross-validation partitions the data into k folds and trains/evaluates the model k times, each time using a different fold as the validation set and the rest as training data. The performance estimate is averaged across folds.
Why it is better than a single split: - A single 80/20 split gives you one estimate of model performance. That estimate depends heavily on which 20% happened to be in the test set. - Cross-validation gives you k estimates and their variance — you get a more reliable picture of how the model will generalise. - For small datasets, cross-validation is especially important because you cannot afford to hold out a large test set permanently.
Important: the test set (your final held-out data) should not be touched until your final evaluation. Cross-validation happens on the training set only. Mixing the two is data leakage.
Practical Machine Learning¶
Q11: How do you handle imbalanced classes?
Show answer
Start by understanding the degree of imbalance and the cost asymmetry.
Step 1 — Choose the right metric. Accuracy is useless on imbalanced data. Use precision, recall, F1, or AUC-ROC depending on whether false positives or false negatives are more costly.
Step 2 — Resampling: - Oversample the minority class: SMOTE generates synthetic minority samples by interpolating between existing ones. Avoid simple random oversampling — it just duplicates rows. - Undersample the majority class: random undersampling loses information. Use only if the majority class is genuinely redundant.
Step 3 — Class weights. Most scikit-learn estimators accept class_weight='balanced', which adjusts the loss function to penalise errors on the minority class more heavily. Try this before resampling — it is cheaper and often sufficient.
Step 4 — Threshold adjustment. By default, classifiers predict the positive class when probability > 0.5. On imbalanced data, you can lower this threshold to increase recall at the cost of precision. Choose the threshold using a precision-recall curve on your validation set.
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report
model = LogisticRegression(class_weight='balanced')
model.fit(X_train, y_train)
# Adjust threshold
probs = model.predict_proba(X_val)[:, 1]
threshold = 0.3
preds = (probs >= threshold).astype(int)
print(classification_report(y_val, preds))
Q12: How do you choose which model to use?
Show answer
There is no formula — it is a decision based on several factors:
| Factor | Implication |
|---|---|
| Dataset size | Small: logistic regression, SVM. Large: tree ensembles, neural nets |
| Interpretability required | Logistic regression, decision tree, linear models |
| Tabular data | Gradient boosting (XGBoost, LightGBM) is the default strong choice |
| Text or images | Neural networks (transformers, CNNs) |
| Training time budget | Logistic regression is fast; neural nets are slow |
| Baseline needed fast | Logistic regression or majority class classifier |
In practice: start with a simple model (logistic regression or decision tree). Understand its failure mode. Move to a more complex model only when you have evidence the simpler model's errors are caused by insufficient model capacity, not by bad features or bad data.
Q13: Your model performs well on training data but poorly on validation data. What do you do?
Show answer
This is a high-variance (overfitting) problem. Work through these in order:
- Verify there is no data leakage: did any features include future information or information derived from the target?
- Increase regularisation: add or strengthen L1/L2 penalty; reduce
max_depth; increasemin_samples_leaf. - Reduce model complexity: a simpler model with fewer parameters is less likely to overfit.
- Get more training data: more data is the most reliable fix for overfitting.
- Feature selection: remove noisy or irrelevant features; too many features relative to samples causes overfitting.
- Ensembling: random forests and boosted models are more resistant to overfitting than single trees.
Check the learning curve: plot training and validation error as a function of training set size. High variance shows as a large gap between the two curves that closes as you add data.
Q14: What is data leakage and how do you prevent it?
Show answer
Data leakage is when information from outside the training period or outside the features that would be available at prediction time is used to train the model. It inflates validation performance and produces models that fail in production.
Common forms: - Target leakage: a feature that is a consequence of the target, not a cause of it. Example: including "claim_filed" as a feature in a fraud model — this column is 1 only after fraud has already been detected. - Temporal leakage: using future data to predict the past. Example: training a demand forecast model using sales data that was collected after the forecast date. - Preprocessing leakage: fitting a scaler or imputer on the full dataset before splitting. The training set "sees" statistics derived from the validation set.
Prevention:
- Split first, then preprocess using only training statistics.
- Use scikit-learn Pipeline — it applies fit only on training data and transform on both.
- Review every feature: "would this value be available at prediction time in production?"
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
# Correct: scaler is fit only on training data
pipe = Pipeline([
('scaler', StandardScaler()),
('clf', LogisticRegression())
])
pipe.fit(X_train, y_train)
pipe.score(X_val, y_val)
SQL¶
Q15: What is a window function? Give an example.
Show answer
A window function computes a value for each row based on a set of related rows — the "window" — without collapsing the result into a single grouped row the way GROUP BY does.
-- Rank customers by total spend within each country
SELECT
customer_id,
country,
total_spend,
RANK() OVER (
PARTITION BY country
ORDER BY total_spend DESC
) AS spend_rank
FROM customers;
Common window functions:
- ROW_NUMBER() — unique sequential rank
- RANK() — same rank for ties, then skips
- DENSE_RANK() — same rank for ties, no skipping
- LAG(col, n) / LEAD(col, n) — access the value n rows before/after
- SUM() OVER (...) / AVG() OVER (...) — running totals or moving averages
Q16: How do you find duplicate rows in a table?
Show answer
-- Find groups of rows with the same key columns appearing more than once
SELECT
email,
COUNT(*) AS occurrences
FROM users
GROUP BY email
HAVING COUNT(*) > 1;
To see the full duplicate rows:
Q17: Write a self-join to find pairs of customers in the same city.
Show answer
SELECT
a.customer_id AS customer_1,
b.customer_id AS customer_2,
a.city
FROM customers a
JOIN customers b
ON a.city = b.city
AND a.customer_id < b.customer_id -- avoid (A,B) and (B,A) duplicates
ORDER BY a.city;
The a.customer_id < b.customer_id condition is the key detail. Without it, every pair appears twice and each customer is paired with themselves.
Q18: What is the difference between WHERE and HAVING?
Show answer
WHEREfilters individual rows before aggregation.HAVINGfilters groups after aggregation.
-- WHERE: filter before grouping
SELECT department, AVG(salary)
FROM employees
WHERE hire_date >= '2020-01-01' -- only recent hires
GROUP BY department;
-- HAVING: filter after grouping
SELECT department, AVG(salary) AS avg_salary
FROM employees
GROUP BY department
HAVING AVG(salary) > 80000; -- only departments with high average
You cannot use aggregate functions in a WHERE clause. If you try, you get a syntax error.
Python and Pandas¶
Q19: What is the difference between .loc and .iloc?
Show answer
.locselects by label (the index value and column name)..ilocselects by integer position (0-based row and column numbers).
import pandas as pd
df = pd.DataFrame({'value': [10, 20, 30]}, index=['a', 'b', 'c'])
df.loc['b'] # row where index label is 'b' → value: 20
df.iloc[1] # second row (position 1) → value: 20
# When the index is default integers, loc and iloc look the same
# but they are different when the index is non-sequential or string-based
df2 = pd.DataFrame({'value': [10, 20, 30]}, index=[5, 10, 15])
df2.loc[10] # row with label 10 → value: 20
df2.iloc[1] # second row → value: 20
df2.loc[1] # KeyError — no label '1' exists
Q20: How do you handle missing values? Walk me through your actual process.
Show answer
Step 1 — understand the extent and pattern:
df.isnull().sum() # count per column
df.isnull().mean().sort_values(ascending=False) # proportion per column
Step 2 — understand why values are missing: - Missing completely at random (MCAR): no relationship between missingness and values - Missing at random (MAR): missingness depends on other observed variables - Missing not at random (MNAR): the fact of missingness is itself informative
Step 3 — choose a strategy based on the cause:
# Drop rows: only when <5% of rows are missing and the column is critical
df.dropna(subset=['target_column'], inplace=True)
# Simple imputation
from sklearn.impute import SimpleImputer
imp = SimpleImputer(strategy='median') # median for numeric, most_frequent for categorical
df[numeric_cols] = imp.fit_transform(df[numeric_cols])
# Indicator column: when missingness itself is informative
df['age_missing'] = df['age'].isnull().astype(int)
# Model-based imputation: IterativeImputer uses other features to predict missing values
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
imp = IterativeImputer(max_iter=10, random_state=0)
df[numeric_cols] = imp.fit_transform(df[numeric_cols])
Always document which strategy you used and why. The choice is a modelling decision with real consequences.
Q21: How do you merge two DataFrames and what is the difference between join types?
Show answer
import pandas as pd
# Inner join: only rows where the key exists in both tables
result = pd.merge(df_left, df_right, on='customer_id', how='inner')
# Left join: all rows from left, NaN where right has no match
result = pd.merge(df_left, df_right, on='customer_id', how='left')
# Outer join: all rows from both, NaN where either has no match
result = pd.merge(df_left, df_right, on='customer_id', how='outer')
# Verify: check for unexpected row count changes after merging
print(f"Left rows: {len(df_left)}, Right rows: {len(df_right)}, Merged rows: {len(result)}")
Warning
If your merged DataFrame has more rows than the left table, you have a one-to-many or many-to-many join you may not have intended. Always check row counts before and after merges.