MBPP › Execution-Based Evaluation
Language AI HandbookAll chapters MBPP › Execution-Based EvaluationOne section · Sections in this chapter4 / 12 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
Both MBPP and HumanEval rely on execution-based evaluation rather than textual similarity metrics like BLEU or ROUGE. Traditional NLP metrics fail for code generation because syntactically different solutions can be semantically identical, while similar-looking code can behave completely differently.
The basic issue is that code is not text to be compared, but instructions to be executed. Two solutions might look completely different, one using list comprehensions and another using explicit loops, yet produce identical outputs for all inputs. Conversely, two code snippets might differ by only a single character, perhaps a less-than versus less-than-or-equal sign, yet produce drastically different behaviors. Execution-based evaluation cuts through this surface variation to measure the relevant behavior: does the code do what it is supposed to do?
This principle sounds obvious but imposes concrete requirements on evaluation infrastructure. Running generated code requires a test harness that can execute Python in a controlled environment, catch exceptions, enforce time limits, and aggregate results across potentially hundreds of candidates per problem. Building this infrastructure correctly involves solving problems in software engineering, security, and systems programming that have nothing to do with natural language processing.
The pass@k Metric
The standard evaluation metric for MBPP is pass@k, which measures the probability that at least one out of generated samples passes all test cases. This metric acknowledges the stochastic nature of language model generation: even capable models might produce incorrect code on any single attempt due to sampling randomness. By evaluating multiple samples and asking whether at least one succeeds, pass@k gives a more complete picture of model capability.
The intuition behind pass@k is straightforward. If you can only see one output from the model (the case ), pass@1 tells you the probability that this single output is correct. This is the most relevant metric for applications where you show users a single suggestion. If you can generate multiple candidates and use a reranker, an execution sandbox, or other selection mechanism to pick the best one (the case ), then pass@k for larger tells you the probability that at least one acceptable solution exists among the candidates.
To compute pass@k rigorously, we treat the selection of samples from total candidates as a hypergeometric sampling problem. Suppose we generate candidate solutions for a problem and find that of them are correct (pass all tests). We want the probability that a random selection of of these candidates contains at least one correct solution. The complement approach yields:
where:
- : the total number of samples generated per problem (typically 200 in the original paper), representing the complete pool of candidate solutions from which we draw
- : the number of correct samples, meaning those that pass all test assertions, representing the successful candidates within the pool
- : the number of samples we select for evaluation (typically 1, 10, or 100), representing how many attempts the model gets before we check for success
- : the number of ways to choose items exclusively from the incorrect candidates, which is all selections containing no correct solution
- : the total number of ways to choose any samples from all candidates
- : the expectation computed by averaging across all problems in the evaluation set
This formula works by computing the complement of the probability of failure. The fraction is the hypergeometric probability of drawing zero successes when selecting items from a population of items containing successes. Subtracting this from 1 yields the probability of obtaining at least one correct sample.
Deriving the Approximation
When is large relative to , the hypergeometric distribution approximates a binomial distribution. In this regime, each draw is approximately independent with success probability . The probability of failing all independent draws is then , giving:
where:
- : the empirical probability that any single randomly generated sample is correct, estimated from the pool of samples
- : the probability under independent Bernoulli sampling that all draws are incorrect
For the specific case of , this simplifies to:
which is simply the fraction of correct samples in the pool. This makes intuitive sense: if 20 out of 100 generated samples are correct, the probability that a randomly chosen single sample is correct is 20%.
For the case where the per-sample success probability is small, we can use the first-order Taylor expansion of the binomial to obtain an even simpler approximation. When :
This linear approximation holds when is small, giving the intuitive result that pass@k scales roughly linearly with both and the base success probability. However, this approximation breaks down as grows, since pass@k is bounded above by 1 but the linear approximation is not.
The key reason for using the exact combinatorial formula rather than the approximation is numerical stability and unbiasedness. The approximation introduces upward bias when is small relative to , precisely the regime where accurate estimation matters most for identifying the capability ceiling of weak models. The exact formula is unbiased regardless of the relationship between and .
The combinatorial formula for pass@k gives an unbiased estimator of the true pass@k probability. If you instead computed and plugged into the approximation , you would get a biased estimate because the expectation of a nonlinear function of a random variable is not the same as the nonlinear function of the expectation. The hypergeometric formulation correctly accounts for the finite population structure and produces unbiased estimates even when is modest. This matters when you are generating 10 or 20 samples per problem rather than 200, which is common when evaluation is expensive.
A Worked Numerical Example
Let us work through a concrete example to build intuition. Suppose we generate candidate solutions for a problem and find that of them pass all test cases. We want to compute pass@k for .
For :
With 4 correct solutions out of 20, we have a 20% chance that any single sample is correct. This equals exactly, confirming the simplified formula for .
For :
With 5 samples, we have a 71.8% chance of including at least one correct solution. The approximation gives , which is noticeably lower due to the finite population correction. Drawing without replacement makes it easier to get diverse samples, which increases the chance of hitting a correct one.
For :
With 10 samples drawn from our pool of 20 (which contains 4 correct solutions), we have a 95.7% chance of selecting at least one correct solution. The large improvement from 20% at to 95.7% at illustrates why the choice of matters so much for comparing models. A model that generates correct code 20% of the time at is not useless: if given 10 attempts and a way to identify the correct solution, it succeeds 96% of the time.
Implementation Considerations
Calculating pass@k requires executing generated code against test cases in a controlled environment. This introduces several engineering challenges that do not exist in text-based evaluation.
from math import comb
def calculate_pass_at_k(n, c, k):
"""
Calculate pass@k given n total samples and c correct samples.
Uses the exact hypergeometric formula for unbiased estimation.
Args:
n: Total number of samples generated per problem
c: Number of correct samples (passing all tests)
k: k value for pass@k
Returns:
pass@k score as a float in [0, 1]
"""
# If we have fewer incorrect samples than k, at least one must be correct
if n - c < k:
return 1.0
return 1.0 - comb(n - c, k) / comb(n, k)
# Demonstrate the worked example from above
n_samples = 20
correct = 4
k_values = [1, 5, 10]
pass_scores = {
f"pass@{k}": calculate_pass_at_k(n_samples, correct, k) for k in k_values
}With n=20 total samples and c=4 correct: pass@1: 0.2000 pass@5: 0.7183 pass@10: 0.9567 Approximation 1 - (1 - c/n)^k: pass@1 approx: 0.2000 pass@5 approx: 0.6723 pass@10 approx: 0.8926
The comparison between the exact formula and the approximation shows the finite population correction at work. For , the exact formula gives 0.7183 while the approximation gives 0.6723. The exact formula is higher because drawing without replacement from a finite population makes it harder to avoid the correct solutions once they exist in the pool.

The large increase from pass@1 to pass@100 illustrates why multiple sampling matters in code generation. With a 20% individual success rate, we reach near-perfect success given 100 attempts. This phenomenon highlights the importance of filtering or ranking mechanisms in practical applications. A system that can identify the best solution from many candidates, perhaps by running all of them and checking which ones produce consistent outputs on diverse inputs, can sharply outperform single-shot generation even when the underlying model quality is moderate.