Precision and Recall

A medium Python interview practice problem on DataDriven. Write the Python and run it against test cases, with instant feedback.

Domain
Python
Difficulty
medium
Seniority
L5
Asked in
Palantir

Problem

An analyst no longer trusts the precision and recall a fraud classifier's dashboard reports, and wants both recomputed straight from the raw 0/1 labels in actual and predicted. A metric with nothing to measure against (no flagged cases, or no real ones) comes back as 0.0.

Precision and recall. Both matter.

Example 1

Input:

actual = [1,0,1,1,0,1,0,0,1,0], predicted = [1,0,0,1,0,1,1,0,1,0]

Output:

{"precision":0.8,"recall":0.8}

Example 2

Input:

actual = [1,1,1,0,0], predicted = [0,0,0,0,0]

Output:

{"precision":0,"recall":0}

Worked solution and explanation

What this really is

This is a three-bucket count dressed up as a model audit. Anyone can recite TP / (TP + FP). The two places candidates actually lose the point are the bucketing logic and the empty denominators. Precision divides by everything the model flagged, recall by everything that was really positive, and either pool can be empty. Skip the guard and the first quiet model, or the first batch with no real fraud in it, raises ZeroDivisionError instead of reporting 0.0.

Building it

Step 1: Sort each pair into one bucket

Walk the two lists in lockstep. A pair where the model flagged it and it was real is a true positive. Once that case is handled, any remaining flag is a false positive, and any remaining real case is a false negative. The elif chain carries the earlier test forward, so the later branches need only one condition each.

Step 2: Ignore true negatives on purpose

Pairs where both values are 0 feed neither metric. Letting them fall through the chain untouched is correct, not an oversight; counting them as false negatives is the bug that drags recall down.

Step 3: Guard each denominator separately

true_pos + false_pos and true_pos + false_neg go to zero under different inputs: a model that flags nothing empties the first, a batch with no real positives empties the second. Each ratio gets its own check and falls back to 0.0.

One pass, three counters, two guards
def precision_and_recall(actual: list[int], predicted: list[int]):
    true_pos = 0
    false_pos = 0
    false_neg = 0
    for i in range(len(actual)):
        if predicted[i] == 1 and actual[i] == 1:
            true_pos += 1
        elif predicted[i] == 1:
            false_pos += 1
        elif actual[i] == 1:
            false_neg += 1

    flagged = true_pos + false_pos
    real = true_pos + false_neg
    precision = true_pos / flagged if flagged > 0 else 0.0
    recall = true_pos / real if real > 0 else 0.0
    return {'precision': precision, 'recall': recall}
Formula straight in
tp / (tp + fp) crashes when the model flags nothing, and tp / (tp + fn) crashes on a batch with no real positives or an empty input.
Guarded
Compute flagged and real once, divide only when each is positive, and report 0.0 otherwise.

Common follow-up questions