Advanced

Practice Problems & Tips

Timed practice challenges to simulate real interview pressure, optimization tips for writing faster code, and an FAQ accordion covering the most common questions candidates ask before ML interviews.

Timed Practice Challenges

Set a timer and try to solve each problem within the allotted time. These simulate the time pressure of real interviews. Solutions follow each problem.

15 min Challenge 1: Implement Batch Normalization

📝
Problem: Implement batch normalization for a 2D input (batch_size, features). Include both training mode (use batch stats) and inference mode (use running stats). Track running mean and variance with momentum.
import numpy as np

class BatchNorm:
    """Batch Normalization layer (NumPy implementation).

    During training: normalize using batch mean/var, update running stats.
    During inference: normalize using running mean/var.
    """

    def __init__(self, num_features, momentum=0.1, eps=1e-5):
        self.gamma = np.ones(num_features)     # Learnable scale
        self.beta = np.zeros(num_features)     # Learnable shift
        self.running_mean = np.zeros(num_features)
        self.running_var = np.ones(num_features)
        self.momentum = momentum
        self.eps = eps
        self.training = True

    def forward(self, X):
        """
        Args:
            X: Shape (batch_size, num_features).
        Returns:
            Normalized X, same shape.
        """
        if self.training:
            mean = X.mean(axis=0)
            var = X.var(axis=0)

            # Update running statistics
            self.running_mean = (1 - self.momentum) * self.running_mean + self.momentum * mean
            self.running_var = (1 - self.momentum) * self.running_var + self.momentum * var
        else:
            mean = self.running_mean
            var = self.running_var

        # Normalize
        X_norm = (X - mean) / np.sqrt(var + self.eps)

        # Scale and shift
        return self.gamma * X_norm + self.beta


# Test
np.random.seed(42)
X_train = np.random.randn(32, 4) * 5 + 3  # Non-zero mean, large variance

bn = BatchNorm(num_features=4)

# Training: should normalize to ~zero mean, ~unit variance
out_train = bn.forward(X_train)
print(f"Train output mean: {out_train.mean(axis=0).round(4)}")  # ~[0, 0, 0, 0]
print(f"Train output std:  {out_train.std(axis=0).round(4)}")   # ~[1, 1, 1, 1]

# Switch to eval mode
bn.training = False
X_test = np.random.randn(8, 4) * 5 + 3
out_eval = bn.forward(X_test)
print(f"Eval output (uses running stats): mean={out_eval.mean(axis=0).round(2)}")

10 min Challenge 2: Top-K Accuracy

📝
Problem: Implement top-k accuracy: the prediction is correct if the true label is among the k highest-probability predictions. This is standard for ImageNet evaluation (top-1 and top-5 accuracy).
import numpy as np

def topk_accuracy(y_true: np.ndarray, y_probs: np.ndarray, k: int = 5) -> float:
    """Compute top-k accuracy.

    Args:
        y_true: True labels, shape (n,).
        y_probs: Predicted probabilities, shape (n, num_classes).
        k: Number of top predictions to consider.

    Returns:
        Top-k accuracy as a float in [0, 1].
    """
    # Get indices of top-k predictions per sample
    top_k_preds = np.argsort(y_probs, axis=1)[:, -k:]  # Last k = highest

    # Check if true label is in top-k
    correct = np.any(top_k_preds == y_true[:, np.newaxis], axis=1)

    return correct.mean()


# Test
np.random.seed(42)
n, num_classes = 100, 10
y_true = np.random.randint(0, num_classes, n)
y_probs = np.random.dirichlet(np.ones(num_classes), n)  # Random probabilities

# Make some predictions "correct" by boosting true class probability
for i in range(60):  # 60% should be easy
    y_probs[i, y_true[i]] += 2.0
y_probs = y_probs / y_probs.sum(axis=1, keepdims=True)  # Re-normalize

top1 = topk_accuracy(y_true, y_probs, k=1)
top5 = topk_accuracy(y_true, y_probs, k=5)
print(f"Top-1 Accuracy: {top1:.4f}")
print(f"Top-5 Accuracy: {top5:.4f}")  # Should be higher than top-1

15 min Challenge 3: Label Encoding with Unseen Categories

📝
Problem: Implement a label encoder that handles unseen categories at test time by mapping them to an “unknown” category. Include fit, transform, and inverse_transform methods.
import numpy as np

class SafeLabelEncoder:
    """Label encoder that handles unseen categories gracefully."""

    def __init__(self):
        self.classes_ = None
        self.class_to_idx_ = None
        self.unknown_idx_ = None

    def fit(self, values):
        """Learn unique classes from training data."""
        unique = sorted(set(values))
        self.classes_ = ['<UNKNOWN>'] + list(unique)
        self.class_to_idx_ = {cls: idx for idx, cls in enumerate(self.classes_)}
        self.unknown_idx_ = 0
        return self

    def transform(self, values):
        """Transform values to integer codes. Unseen -> 0."""
        return np.array([
            self.class_to_idx_.get(v, self.unknown_idx_)
            for v in values
        ])

    def inverse_transform(self, codes):
        """Convert integer codes back to original values."""
        return np.array([
            self.classes_[c] if 0 <= c < len(self.classes_) else '<UNKNOWN>'
            for c in codes
        ])

    def fit_transform(self, values):
        return self.fit(values).transform(values)


# Test
encoder = SafeLabelEncoder()
train_data = ['cat', 'dog', 'bird', 'cat', 'dog', 'fish']
test_data = ['cat', 'dog', 'hamster', 'snake', 'bird']  # hamster, snake are unseen

encoded_train = encoder.fit_transform(train_data)
encoded_test = encoder.transform(test_data)

print(f"Classes: {encoder.classes_}")
print(f"Train encoded: {encoded_train}")
print(f"Test encoded:  {encoded_test}")  # hamster, snake -> 0 (unknown)
print(f"Test decoded:  {encoder.inverse_transform(encoded_test)}")

12 min Challenge 4: Stratified Train/Test Split

📝
Problem: Implement stratified train/test split from scratch. The class distribution in both train and test sets should match the overall distribution. Do not use sklearn.
import numpy as np
from collections import Counter

def stratified_split(X, y, test_size=0.2, random_state=42):
    """Stratified train/test split maintaining class proportions.

    Args:
        X: Feature array of shape (n, d).
        y: Label array of shape (n,).
        test_size: Fraction of data for test set.
        random_state: Random seed.

    Returns:
        X_train, X_test, y_train, y_test
    """
    rng = np.random.RandomState(random_state)
    classes = np.unique(y)

    train_indices = []
    test_indices = []

    for cls in classes:
        # Get indices for this class
        cls_indices = np.where(y == cls)[0]
        rng.shuffle(cls_indices)

        # Split this class
        n_test = max(1, int(len(cls_indices) * test_size))
        test_indices.extend(cls_indices[:n_test])
        train_indices.extend(cls_indices[n_test:])

    # Shuffle the final indices
    train_indices = np.array(train_indices)
    test_indices = np.array(test_indices)
    rng.shuffle(train_indices)
    rng.shuffle(test_indices)

    return X[train_indices], X[test_indices], y[train_indices], y[test_indices]


# Test with imbalanced data
np.random.seed(42)
X = np.random.randn(200, 5)
y = np.array([0]*160 + [1]*30 + [2]*10)  # 80/15/5 split

X_train, X_test, y_train, y_test = stratified_split(X, y, test_size=0.2)

print(f"Original distribution: {dict(Counter(y))}")
print(f"Train distribution:    {dict(Counter(y_train))} (n={len(y_train)})")
print(f"Test distribution:     {dict(Counter(y_test))} (n={len(y_test)})")
# Class ratios should be approximately maintained

20 min Challenge 5: End-to-End Mini Pipeline

📝
Problem: Given raw data, build a complete mini ML pipeline from scratch: handle missing values, encode categoricals, normalize numerics, train a logistic regression model, and evaluate with precision/recall/F1. Use only NumPy and Pandas (no sklearn).
import numpy as np
import pandas as pd

# Raw data
df = pd.DataFrame({
    'age': [25, 30, np.nan, 45, 22, 35, np.nan, 50, 28, 40],
    'income': [30000, 50000, 60000, np.nan, 25000, 55000, 45000, 80000, np.nan, 65000],
    'education': ['HS', 'BS', 'MS', 'PhD', 'HS', 'BS', 'MS', 'PhD', 'BS', 'MS'],
    'purchased': [0, 0, 1, 1, 0, 1, 0, 1, 0, 1]
})

# Step 1: Handle missing values (median for numeric)
for col in ['age', 'income']:
    median_val = df[col].median()
    df[col] = df[col].fillna(median_val)

# Step 2: One-hot encode categoricals
education_dummies = pd.get_dummies(df['education'], prefix='edu', drop_first=True)
df = pd.concat([df.drop('education', axis=1), education_dummies], axis=1)

# Step 3: Normalize numeric features
for col in ['age', 'income']:
    mean_val = df[col].mean()
    std_val = df[col].std()
    df[col] = (df[col] - mean_val) / (std_val if std_val > 0 else 1)

# Step 4: Split data
X = df.drop('purchased', axis=1).values.astype(float)
y = df['purchased'].values

# Simple train/test (first 8 train, last 2 test for this small demo)
X_train, X_test = X[:8], X[8:]
y_train, y_test = y[:8], y[8:]

# Step 5: Logistic regression from scratch
def sigmoid(z):
    return 1 / (1 + np.exp(-np.clip(z, -500, 500)))

# Train
lr = 0.1
w = np.zeros(X_train.shape[1])
b = 0.0

for epoch in range(1000):
    z = X_train @ w + b
    pred = sigmoid(z)
    error = pred - y_train
    w -= lr * (X_train.T @ error) / len(y_train)
    b -= lr * error.mean()

# Step 6: Predict and evaluate
y_pred_prob = sigmoid(X_test @ w + b)
y_pred = (y_pred_prob >= 0.5).astype(int)

# Metrics from scratch
tp = np.sum((y_pred == 1) & (y_test == 1))
fp = np.sum((y_pred == 1) & (y_test == 0))
fn = np.sum((y_pred == 0) & (y_test == 1))

precision = tp / (tp + fp) if (tp + fp) > 0 else 0
recall = tp / (tp + fn) if (tp + fn) > 0 else 0
f1 = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0

print(f"Predictions: {y_pred}, Actual: {y_test}")
print(f"Precision: {precision:.4f}")
print(f"Recall: {recall:.4f}")
print(f"F1 Score: {f1:.4f}")

Optimization Tips for Interview Day

Time Management

Spend 20% of time understanding the problem, 60% coding, 20% testing. If stuck for more than 3 minutes, ask for a hint — interviewers prefer candidates who communicate over those who silently struggle.

📝

Start with Brute Force

Get a working solution first, then optimize. A correct O(n²) solution that you explain well beats an incorrect O(n) attempt. Always tell the interviewer: “I will start with a simple approach, then optimize.”

💬

Think Out Loud

Narrate your thought process: “I am going to use broadcasting here because...” This lets the interviewer evaluate your reasoning even if you make a minor syntax error.

🐛

Debug Systematically

When code does not work, print shapes first (print(X.shape)), then intermediate values. 80% of ML bugs are shape mismatches. Stay calm and methodical.

The Day-Before Checklist

CategoryItems to Review
NumPyBroadcasting rules, @ operator, einsum, argpartition, keepdims
Pandasgroupby + agg, merge types, pivot_table vs crosstab, transform vs apply
SklearnPipeline + ColumnTransformer, custom transformer template, cross_val_score, data leakage prevention
PyTorchTraining loop (5 steps), Dataset/DataLoader, model.eval(), torch.no_grad()
Common PatternsSoftmax with max subtraction, sigmoid from logits, MSE/CE loss gradients, one-hot encoding tricks
Edge CasesEmpty arrays, zero division, NaN handling, shape (n,) vs (n,1), float32 vs float64

Frequently Asked Questions

Click any question to expand the answer.

Should I use classes or functions in my interview solutions?

Use a class structure when implementing ML models (it mirrors the sklearn API that interviewers expect: __init__, fit, predict). Use functions for data processing utilities and one-off computations. When in doubt, ask the interviewer: “Would you prefer a class-based or functional approach?”

What if I forget a specific NumPy or Pandas function name?

This happens to everyone. Say: “I know there is a function that does X, let me describe what it does.” Then write pseudocode or a comment explaining the behavior. Interviewers care about your understanding of the concept, not whether you memorized every API name. You can also say: “I would look this up in practice, but the approach is...”

How should I handle a question I have never seen before?

Break it down into sub-problems you do recognize. Most ML coding questions combine 2–3 patterns you already know. Say: “I have not seen this exact problem, but it looks like a combination of X and Y. Let me start with...” Interviewers are testing your problem-solving process, not whether you have seen every possible question.

Is it okay to use Google/StackOverflow during the interview?

It depends on the format. In live coding interviews (screen share), you generally cannot. In take-home assignments, looking up documentation is expected (but copying entire solutions is not). For in-person/video interviews, some companies allow you to reference official documentation (numpy.org, pandas docs). Always ask the interviewer at the start: “Can I reference documentation if I need to check a function signature?”

What is the single most important thing to practice?

Vectorized thinking with NumPy. The ability to replace a Python loop with a NumPy operation is the strongest signal of ML programming fluency. Practice converting for-loops into broadcasting, matrix multiplication, or einsum operations. If you can do this naturally, you will pass the Python fluency bar at any company.

How many practice problems should I solve before the interview?

Quality over quantity. Solve 20–30 problems deeply (understand every line of the solution, be able to reproduce it from memory) rather than rushing through 100 problems superficially. The challenges in this course cover the most common patterns. If you can solve all 58 challenges in this course without looking at the solutions, you are well prepared.

What if my code runs but gives wrong results?

Do not panic. This is normal and interviewers expect it. Follow this debugging protocol: (1) Print intermediate variable shapes, (2) Check the first few values of each variable, (3) Test with a trivial input where you know the answer, (4) Check axis parameters (axis=0 vs axis=1 is the most common bug). Say: “Let me add some debug prints to isolate the issue.” This shows systematic debugging skills.

Should I optimize for readability or performance?

Readability first, always. Write clean, well-named code that works correctly. Then, if time permits, mention optimizations: “This is O(n²), but I could reduce it to O(n) by using a hash map.” The interviewer values that you know the optimization exists more than whether you implemented it. Exception: if the problem specifically asks for an efficient solution, optimize from the start.

How do ML coding interviews differ at startups vs FAANG?

FAANG companies have more standardized formats: 45–60 minutes, specific problem types, structured rubrics. Startups tend to be more open-ended: “Here is a dataset, build something useful.” Startups may also give take-home assignments (2–4 hours) that test end-to-end skills. For FAANG, practice algorithmic ML problems. For startups, practice building complete pipelines with messy data.

What should I do in the last 5 minutes of the interview?

If your code works: run a test case, discuss edge cases you handled, and mention improvements you would make with more time (“I would add type hints, handle larger-than-memory data, add logging”). If your code does not work: explain what you think the bug is and how you would fix it. Having a clear plan shows competence even if time runs out. Never spend the last minutes silently staring at code.

Final Review: Python for ML Interviews Cheat Sheet

NumPy Essentials

# Broadcasting: (n,1) op (1,m) -> (n,m)
# Matrix multiply: X @ W (not np.dot for 2D+)
# Softmax: exp(x - x.max()) / sum(exp(x - x.max()))
# Distances: ||a-b||^2 = ||a||^2 + ||b||^2 - 2*a.b
# One-hot: np.eye(num_classes)[labels]
# Top-k: np.argpartition(arr, -k)[:, -k:]  # O(n) not O(n log n)
# Stable sigmoid: use piecewise for pos/neg z

Pandas Essentials

# Named aggregation: df.groupby('col').agg(name=('col', 'func'))
# Transform (same shape): df.groupby('g')['v'].transform('mean')
# Merge indicator: df1.merge(df2, indicator=True)
# Pivot: pd.pivot_table(df, index=, columns=, values=, aggfunc=)
# Melt (unpivot): df.melt(id_vars=, value_vars=)
# Rolling: df['col'].rolling(window=7, min_periods=1).mean()
# Sessionize: df.groupby('user')['time'].diff() > threshold -> cumsum()

Sklearn Essentials

# Pipeline: Pipeline([('step', Transformer()), ('model', Model())])
# ColumnTransformer: for mixed types (numeric + categorical)
# Custom transformer: BaseEstimator + TransformerMixin, fit/transform
# Grid search params: 'step__param_name'
# CRITICAL: all preprocessing inside Pipeline to prevent data leakage
# Stratified CV: StratifiedKFold for classification

PyTorch Essentials

# Training loop: zero_grad() -> forward -> loss -> backward -> step
# model.train() for training, model.eval() for validation
# torch.no_grad() during validation
# Custom Dataset: __init__, __len__, __getitem__
# Gradient clipping: torch.nn.utils.clip_grad_norm_()
# Transfer learning: freeze backbone, replace head, differential LR
💡
You are ready. If you have worked through all 58 challenges in this course and can solve the timed problems above within the time limits, you are well-prepared for Python coding questions in ML interviews. Remember: interviewers are looking for clear thinking, clean code, and good communication — not perfection. Good luck!