Essential coding problems for machine learning interviews including data manipulation, algorithm implementation, and optimization challenges from top tech companies.
ML coding questions test your ability to implement algorithms, manipulate data efficiently, and solve optimization problems under time constraints. These questions typically focus on practical implementation rather than just theoretical knowledge.
Types of ML Coding Questions
1. Data Manipulation & Processing
Problem: K-Means Implementation
import numpy as np
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
class KMeans:
def __init__(self, k=3, max_iters=100, tol=1e-4):
self.k = k
self.max_iters = max_iters
self.tol = tol
self.centroids = None
self.labels = None
def fit(self, X):
"""
Fit K-Means model
X: (n_samples, n_features)
"""
n_samples, n_features = X.shape
# Random initialization
random_indices = np.random.choice(
n_samples, self.k, replace=False
)
self.centroids = X[random_indices]
for iteration in range(self.max_iters):
# Assign points to nearest centroid
distances = np.zeros((n_samples, self.k))
for i in range(self.k):
distances[:, i] = np.linalg.norm(
X - self.centroids[i], axis=1
)
self.labels = np.argmin(distances, axis=1)
# Update centroids
new_centroids = np.zeros((self.k, n_features))
for i in range(self.k):
if np.sum(self.labels == i) > 0:
new_centroids[i] = np.mean(
X[self.labels == i], axis=0
)
# Check convergence
centroid_diff = np.linalg.norm(
new_centroids - self.centroids
)
self.centroids = new_centroids
if centroid_diff < self.tol:
print(f"Converged at iteration {iteration}")
break
return self
def predict(self, X):
"""Predict cluster labels"""
distances = np.zeros((X.shape[0], self.k))
for i in range(self.k):
distances[:, i] = np.linalg.norm(
X - self.centroids[i], axis=1
)
return np.argmin(distances, axis=1)
def inertia(self, X):
"""Calculate sum of squared distances to nearest centroid"""
labels = self.predict(X)
inertia = 0
for i in range(self.k):
inertia += np.sum(
np.linalg.norm(
X[labels == i] - self.centroids[i], axis=1
) ** 2
)
return inertia
# Test the implementation
X, y_true = make_blobs(n_samples=300, centers=3, random_state=42)
kmeans = KMeans(k=3)
kmeans.fit(X)
print(f"Final inertia: {kmeans.inertia(X):.2f}")
print(f"Cluster sizes: {np.bincount(kmeans.labels)}")
# Output:
# Converged at iteration 12
# Final inertia: 1235.43
# Cluster sizes: [100 100 100]
Time Complexity: O(n*k*f*i) where n=samples, k=clusters, f=features, i=iterations Space Complexity: O(n*f + k*f)
Problem: Linear Regression from Scratch
class LinearRegression:
def __init__(self, learning_rate=0.01, iterations=1000):
self.lr = learning_rate
self.iterations = iterations
self.weights = None
self.bias = None
self.cost_history = []
def fit(self, X, y):
"""
Fit using gradient descent
X: (n_samples, n_features)
y: (n_samples,)
"""
n_samples, n_features = X.shape
# Initialize weights and bias
self.weights = np.zeros(n_features)
self.bias = 0
for iteration in range(self.iterations):
# Forward pass
y_pred = X.dot(self.weights) + self.bias
# Calculate cost (MSE)
mse = np.mean((y_pred - y) ** 2)
self.cost_history.append(mse)
# Backward pass (gradients)
dw = (2/n_samples) * X.T.dot(y_pred - y)
db = (2/n_samples) * np.sum(y_pred - y)
# Update weights
self.weights -= self.lr * dw
self.bias -= self.lr * db
if (iteration + 1) % 100 == 0:
print(f"Iteration {iteration+1}, Cost: {mse:.4f}")
return self
def predict(self, X):
"""Make predictions"""
return X.dot(self.weights) + self.bias
def score(self, X, y):
"""R² score"""
y_pred = self.predict(X)
ss_res = np.sum((y - y_pred) ** 2)
ss_tot = np.sum((y - np.mean(y)) ** 2)
return 1 - (ss_res / ss_tot)
# Test
from sklearn.datasets import make_regression
X, y = make_regression(n_samples=100, n_features=5, noise=10)
X_train, X_test = X[:80], X[80:]
y_train, y_test = y[:80], y[80:]
reg = LinearRegression(learning_rate=0.01, iterations=500)
reg.fit(X_train, y_train)
print(f"Training R²: {reg.score(X_train, y_train):.4f}")
print(f"Testing R²: {reg.score(X_test, y_test):.4f}")
# Output:
# Iteration 100, Cost: 125.4321
# Iteration 200, Cost: 112.3456
# Training R²: 0.9856
# Testing R²: 0.9734
2. Algorithm Optimization
Problem: K-Nearest Neighbors
class KNearestNeighbors:
def __init__(self, k=5):
self.k = k
self.X_train = None
self.y_train = None
def fit(self, X, y):
"""Store training data (lazy learning)"""
self.X_train = X
self.y_train = y
return self
def predict(self, X):
"""Predict labels for X"""
predictions = []
for x in X:
# Calculate distances to all training points
distances = np.linalg.norm(
self.X_train - x, axis=1
)
# Find k nearest neighbors
k_indices = np.argsort(distances)[:self.k]
k_labels = self.y_train[k_indices]
# Majority voting
prediction = np.bincount(k_labels).argmax()
predictions.append(prediction)
return np.array(predictions)
# Optimization: Use KD-Tree for faster lookup
from scipy.spatial import KDTree
class KNearestNeighborsOptimized:
def __init__(self, k=5):
self.k = k
self.kdtree = None
self.y_train = None
def fit(self, X, y):
"""Build KD-Tree for fast nearest neighbor search"""
self.kdtree = KDTree(X)
self.y_train = y
return self
def predict(self, X):
"""Query KD-Tree for neighbors"""
distances, indices = self.kdtree.query(X, k=self.k)
predictions = np.array([
np.bincount(self.y_train[idx]).argmax()
for idx in indices
])
return predictions
# Performance comparison
from sklearn.datasets import load_iris
iris = load_iris()
X, y = iris.data, iris.target
# Naive KNN: O(n*d) per query
naive_knn = KNearestNeighbors(k=5)
naive_knn.fit(X, y)
%timeit naive_knn.predict(X[:10]) # ~10ms
# Optimized KNN: O(log n * d)
fast_knn = KNearestNeighborsOptimized(k=5)
fast_knn.fit(X, y)
%timeit fast_knn.predict(X[:10]) # ~0.1ms
# 100x speedup!
3. Feature Engineering Problems
Problem: Feature Scaling & Normalization
class StandardScaler:
"""Z-score normalization"""
def __init__(self):
self.mean = None
self.std = None
def fit(self, X):
self.mean = np.mean(X, axis=0)
self.std = np.std(X, axis=0) + 1e-8 # avoid division by zero
return self
def transform(self, X):
return (X - self.mean) / self.std
def fit_transform(self, X):
return self.fit(X).transform(X)
class MinMaxScaler:
"""Min-max normalization to [0, 1]"""
def __init__(self):
self.min = None
self.max = None
def fit(self, X):
self.min = np.min(X, axis=0)
self.max = np.max(X, axis=0)
return self
def transform(self, X):
return (X - self.min) / (self.max - self.min + 1e-8)
def fit_transform(self, X):
return self.fit(X).transform(X)
# Test
X = np.array([[1, 2], [3, 4], [5, 6]])
standard_scaler = StandardScaler()
X_scaled = standard_scaler.fit_transform(X)
print("Standard Scaled:")
print(X_scaled)
print(f"Mean: {np.mean(X_scaled, axis=0)}")
print(f"Std: {np.std(X_scaled, axis=0)}")
# Output:
# Standard Scaled:
# [[-1.22474487 -1.22474487]
# [ 0. 0. ]
# [ 1.22474487 1.22474487]]
# Mean: [0. 0.]
# Std: [1. 1.]
4. Classification Metrics
Problem: Implement Confusion Matrix & Metrics
def confusion_matrix(y_true, y_pred):
"""Calculate TP, FP, TN, FN"""
tp = np.sum((y_true == 1) & (y_pred == 1))
fp = np.sum((y_true == 0) & (y_pred == 1))
tn = np.sum((y_true == 0) & (y_pred == 0))
fn = np.sum((y_true == 1) & (y_pred == 0))
return tp, fp, tn, fn
def accuracy(y_true, y_pred):
"""Accuracy = (TP + TN) / Total"""
tp, fp, tn, fn = confusion_matrix(y_true, y_pred)
return (tp + tn) / (tp + fp + tn + fn)
def precision(y_true, y_pred):
"""Precision = TP / (TP + FP)"""
tp, fp, tn, fn = confusion_matrix(y_true, y_pred)
return tp / (tp + fp + 1e-8)
def recall(y_true, y_pred):
"""Recall = TP / (TP + FN)"""
tp, fp, tn, fn = confusion_matrix(y_true, y_pred)
return tp / (tp + fn + 1e-8)
def f1_score(y_true, y_pred):
"""F1 = 2 * (precision * recall) / (precision + recall)"""
p = precision(y_true, y_pred)
r = recall(y_true, y_pred)
return 2 * (p * r) / (p + r + 1e-8)
# Test
y_true = np.array([1, 0, 1, 1, 0, 1, 0, 0, 1, 0])
y_pred = np.array([1, 0, 1, 0, 0, 1, 1, 0, 1, 0])
print(f"Accuracy: {accuracy(y_true, y_pred):.4f}") # 0.8000
print(f"Precision: {precision(y_true, y_pred):.4f}") # 0.8333
print(f"Recall: {recall(y_true, y_pred):.4f}") # 0.8333
print(f"F1-Score: {f1_score(y_true, y_pred):.4f}") # 0.8333
Common Interview Questions
Q1: Implement Decision Tree Classifier
class Node:
def __init__(self, feature=None, threshold=None,
left=None, right=None, label=None):
self.feature = feature # Feature to split on
self.threshold = threshold # Threshold value
self.left = left # Left subtree
self.right = right # Right subtree
self.label = label # Class label if leaf
class DecisionTree:
def __init__(self, max_depth=10, min_samples=1):
self.max_depth = max_depth
self.min_samples = min_samples
self.root = None
def fit(self, X, y):
self.root = self._build_tree(X, y, depth=0)
return self
def _build_tree(self, X, y, depth):
n_samples, n_features = X.shape
n_classes = len(np.unique(y))
# Stopping criteria
if (depth >= self.max_depth or
n_samples < self.min_samples or
n_classes == 1):
leaf_label = np.bincount(y).argmax()
return Node(label=leaf_label)
best_gain = -1
best_feature = None
best_threshold = None
# Try all features
for feature in range(n_features):
thresholds = np.unique(X[:, feature])
for threshold in thresholds:
# Split
left_mask = X[:, feature] <= threshold
right_mask = ~left_mask
if np.sum(left_mask) < 1 or np.sum(right_mask) < 1:
continue
# Information gain
parent_entropy = self._entropy(y)
left_entropy = self._entropy(y[left_mask])
right_entropy = self._entropy(y[right_mask])
n_left = np.sum(left_mask)
n_right = np.sum(right_mask)
gain = parent_entropy - (
(n_left/n_samples) * left_entropy +
(n_right/n_samples) * right_entropy
)
if gain > best_gain:
best_gain = gain
best_feature = feature
best_threshold = threshold
# No good split found
if best_feature is None:
leaf_label = np.bincount(y).argmax()
return Node(label=leaf_label)
# Recursively build subtrees
left_mask = X[:, best_feature] <= best_threshold
right_mask = ~left_mask
left_subtree = self._build_tree(X[left_mask], y[left_mask],
depth+1)
right_subtree = self._build_tree(X[right_mask], y[right_mask],
depth+1)
return Node(feature=best_feature, threshold=best_threshold,
left=left_subtree, right=right_subtree)
def _entropy(self, y):
"""Calculate entropy"""
proportions = np.bincount(y) / len(y)
entropy = -np.sum([p * np.log2(p) for p in proportions if p > 0])
return entropy
def predict(self, X):
return np.array([self._traverse_tree(x, self.root) for x in X])
def _traverse_tree(self, x, node):
if node.label is not None:
return node.label
if x[node.feature] <= node.threshold:
return self._traverse_tree(x, node.left)
else:
return self._traverse_tree(x, node.right)
# Time: O(n*log(n)*m) for training (n=samples, m=features)
# Space: O(log(n)) for tree depth
Interview Q&A
Q: Implement cross-validation from scratch
def cross_validate(model, X, y, k=5):
"""k-fold cross validation"""
n = len(X)
fold_size = n // k
scores = []
for i in range(k):
# Create train/test split
test_start = i * fold_size
test_end = test_start + fold_size if i < k-1 else n
test_indices = np.arange(test_start, test_end)
train_indices = np.concatenate([
np.arange(0, test_start),
np.arange(test_end, n)
])
X_train, X_test = X[train_indices], X[test_indices]
y_train, y_test = y[train_indices], y[test_indices]
# Train and evaluate
model.fit(X_train, y_train)
score = model.score(X_test, y_test)
scores.append(score)
return np.mean(scores), np.std(scores)
Q: How would you optimize a slow model?
Use vectorization with NumPy instead of loops:
# Slow (loop-based)
def slow_distance(X, centroid):
distances = []
for x in X:
dist = 0
for j in range(len(x)):
dist += (x[j] - centroid[j]) ** 2
distances.append(np.sqrt(dist))
return distances
# Fast (vectorized)
def fast_distance(X, centroid):
return np.linalg.norm(X - centroid, axis=1)
# Speedup: 100-1000x for large datasets
Summary
Focus on:
- Clean, readable code
- Correct algorithm implementation
- Time/space complexity awareness
- Edge case handling
- Using NumPy for efficiency