Quick summary
Summarize this blog with AI
Data science interviews often combine Python reasoning with questions about whether an analysis is valid. These six exercises make that combination concrete. Each has a prompt, complete inputs, a worked answer, and a scoring guide. The code uses Python 3.12.14 and the standard library; no downloaded dataset or model account is required.
Give yourself two points per question: one for the correct output and one for the reasoning or limitation. The 12-point total is a practice aid, not an employer's hiring score. For pandas API drills, use the separate pandas question set.
1. Why does a default list retain previous calls?
Prompt: Predict the result of two calls, then repair the helper so omitted arguments get independent lists while a supplied list can still be extended intentionally.
def collect_bad(value, values=[]):
values.append(value)
return values
print(collect_bad("a"))
print(collect_bad("b"))
def collect(value, values=None):
if values is None:
values = []
values.append(value)
return values
print(collect("a"))
print(collect("b"))
Expected output:
['a']
['a', 'b']
['a']
['b']
The default expression is evaluated when the function is defined, so the first helper reuses one list. None is a sentinel in the corrected helper. Full credit includes stating that passing an existing list still mutates that list. If the contract should never mutate input, copy it explicitly instead.
2. Find the first repeated identifier
Prompt: Return the first ID encountered for a second time; return None if no repeat exists. Preserve input order and explain complexity.
def first_repeat(ids):
seen = set()
for item in ids:
if item in seen:
return item
seen.add(item)
return None
print(first_repeat(["b", "a", "c", "a", "b"]))
print(first_repeat([]))
print(first_repeat(["a", "b"]))
Expected output:
a
None
None
The contract uses non-null string IDs, which are hashable. Expected work is linear in the consumed items with average constant-time set membership; memory grows with distinct IDs. This is not constant-memory deduplication, and arbitrary unhashable objects require a different contract. Sorting would change the definition of “first encountered.”
3. Fit an imputation value without test leakage
Prompt: Fill missing values in both splits using the observed training mean. Show why fitting on the combined data changes the answer.
from statistics import mean
train = [1.0, None, 3.0]
test = [100.0, None]
observed_train = [x for x in train if x is not None]
if not observed_train:
raise ValueError("training column has no observed values")
fitted_mean = mean(observed_train)
filled_train = [fitted_mean if x is None else x for x in train]
filled_test = [fitted_mean if x is None else x for x in test]
print(fitted_mean)
print(filled_train)
print(filled_test)
print(round(mean(x for x in train + test if x is not None), 3))
Expected output:
2.0
[1.0, 2.0, 3.0]
[100.0, 2.0]
34.667
The test value changes the combined mean to 34.667, so using that mean would let evaluation data influence preprocessing. Learn transformations within each training fold and reuse their fitted state on its validation fold. The example establishes the leakage rule; mean imputation is not always statistically appropriate, and an entirely missing training feature needs an explicit policy.
4. Can 90% accuracy describe a useless positive-class detector?
Prompt: Compute a confusion matrix, accuracy, precision, and recall for ten binary labels when the model predicts every observation as negative.
def binary_metrics(actual, predicted):
if not actual or len(actual) != len(predicted):
raise ValueError("need nonempty equal-length labels")
if any(type(x) is not int or x not in (0, 1)
for x in actual + predicted):
raise ValueError("labels must be integer 0 or 1")
tp = sum(a == 1 and p == 1 for a, p in zip(actual, predicted))
fp = sum(a == 0 and p == 1 for a, p in zip(actual, predicted))
fn = sum(a == 1 and p == 0 for a, p in zip(actual, predicted))
tn = sum(a == 0 and p == 0 for a, p in zip(actual, predicted))
return {"tp": tp, "fp": fp, "fn": fn, "tn": tn,
"accuracy": (tp + tn) / len(actual),
"precision": tp / (tp + fp) if tp + fp else None,
"recall": tp / (tp + fn) if tp + fn else None}
print(binary_metrics([0] * 9 + [1], [0] * 10))
Expected output:
{'tp': 0, 'fp': 0, 'fn': 1, 'tn': 9, 'accuracy': 0.9, 'precision': None, 'recall': 0.0}
The detector misses every positive. Precision is undefined because it predicted no positives; this helper reports None rather than silently selecting zero. Some libraries use a configurable zero-division convention, so name that convention when comparing results. Accuracy alone hides the positive-class failure in this imbalanced fixture.
5. How does the classification threshold change errors?
Prompt: Apply two thresholds using score >= threshold. Explain why choosing the best-looking threshold on the final test set would be leakage.
actual = [0, 1, 1, 0]
scores = [0.2, 0.4, 0.8, 0.6]
for threshold in [0.5, 0.3]:
predicted = [int(score >= threshold) for score in scores]
result = binary_metrics(actual, predicted)
print(threshold, predicted, result["fp"], result["fn"])
Expected output:
0.5 [0, 0, 1, 1] 1 1
0.3 [0, 1, 1, 1] 1 0
Lowering the threshold recovers the missed positive here without removing the false positive. That is a fixture result, not a guarantee for every population. Choose the threshold using training/validation evidence and the relative costs of errors, then evaluate the frozen choice once on held-out data. Scores need not be calibrated probabilities simply because they fall between zero and one.
6. Is the split independent at the entity level?
Prompt: A model should generalize to unseen customers. Detect whether the proposed training and test split shares customer IDs.
train_customers = ["c1", "c1", "c2"]
test_customers = ["c2", "c3"]
print(sorted(set(train_customers) & set(test_customers)))
print(set(["c1", "c2"]).isdisjoint(["c3", "c4"]))
Expected output:
['c2']
True
The shared c2 violates an unseen-customer evaluation design. Split by customer when that is the intended deployment population. For forecasting, respect temporal ordering and feature availability instead. Group separation alone does not prevent a feature from containing future outcomes; inspect how and when every feature is constructed.
Explain the assumptions that make the answer valid
Review wrong answers as diagnostic signals: shared default state, quadratic repeated scans, preprocessing before splitting, accuracy without class counts, threshold tuning on the test set, and row-level splits that leak entities. A strong candidate can identify what the small fixture proves and what needs representative data, uncertainty estimates, or a different evaluation design.
References: default arguments, sets, scikit-learn's leakage guidance, and classification metric definitions.