Code Generation › Code Implementation
Language AI HandbookAll chapters Code Generation › Code ImplementationOne section · Sections in this chapter9 / 13 ↓
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
This section walks through practical implementations of docstring-to-code evaluation, sampling strategies, and a simple self-repair loop. We use standard Python for execution and verification, keeping the focus on the core logic rather than model API calls.
Setup
Evaluating Code Generation Quality with Pass@k
The core evaluation metric for code generation is pass@k: given candidate solutions per problem, what fraction of problems is solved by at least one candidate? We implement this metric directly.
The mathematically unbiased estimator for pass@k, as described in the HumanEval paper, avoids the variance of direct sampling by using the formula:
where:
- : total number of generated samples per problem
- : number of correct samples among the
- : the in pass@k (the number of attempts)
- : ways to choose from the incorrect samples
- : ways to choose any from all samples
This estimator is unbiased because it considers all possible ways of selecting samples from the generated, rather than directly sampling times, which would have higher variance. The formula computes the expected fraction of -sample draws that contain at least one correct sample, which is exactly the quantity pass@k is trying to measure.
To see why this is useful: if you generate 10 candidates per problem and 4 are correct, you do not want to compute pass@5 by randomly drawing 5 candidates and checking if any are correct (this would have high variance). Instead, the closed-form expression tells you exactly what fraction of all possible 5-candidate draws would include at least one correct solution.
import math
import numpy as np
def pass_at_k(n: int, c: int, k: int) -> float:
"""
Compute the unbiased pass@k estimator.
n: total samples generated
c: number of correct samples
k: k in pass@k
"""
if n - c < k:
return 1.0
# Use log-space computation to avoid overflow with large n
log_numerator = sum(math.log(n - c - i) for i in range(k))
log_denominator = sum(math.log(n - i) for i in range(k))
return 1.0 - math.exp(log_numerator - log_denominator)
def compute_pass_at_k_results(results, k_values):
"""
Compute pass@k for a set of problem results.
results: list of dicts with 'n' (total) and 'c' (correct) keys
k_values: list of k values to compute
"""
metrics = {}
for k in k_values:
scores = [pass_at_k(r["n"], r["c"], k) for r in results if r["n"] >= k]
metrics[f"pass@{k}"] = np.mean(scores) if scores else 0.0
return metricspass@1: 0.382 pass@2: 0.583 pass@5: 0.841 pass@10: 0.940
The pass@k scores show a clear trend: with more attempts per problem, performance improves substantially, showing why best-of-N selection is so effective in practice.
Simulating Best-of-N Selection
We can visualize how pass@k improves with more samples for problems at different difficulty levels. This helps build intuition for why generating multiple candidates is such a cost-effective way to improve output quality.
def simulate_pass_at_k_curve(p_correct, max_k=20):
"""
Compute theoretical pass@k for a problem where each
attempt independently succeeds with probability p_correct.
"""
return [1 - (1 - p_correct) ** k for k in range(1, max_k + 1)]
The curves make clear why best-of-N is so valuable. A problem that only 25% of individual generations solve is solved about 76% of the time with five independent candidates and crosses 90% at nine candidates. The diminishing returns are also visible: for easy problems (p=0.7), the second candidate raises the solve rate from 70% to 91%, while later samples add progressively less.
Temperature and Sampling Strategy
Temperature directly controls the diversity of generated code. We can demonstrate this by sampling tokens from a synthetic probability distribution and observing how temperature changes the distribution of selections.
def sample_with_temperature(logits, temperature, n_samples=1000):
"""
Sample from a distribution parameterized by logits at a given temperature.
Returns an array of sampled indices.
"""
if temperature == 0:
return np.full(n_samples, np.argmax(logits))
scaled_logits = logits / temperature
# Numerical stability: subtract max before exp
scaled_logits = scaled_logits - np.max(scaled_logits)
probs = np.exp(scaled_logits)
probs = probs / probs.sum()
return np.random.choice(len(logits), size=n_samples, p=probs)

At low temperature, the two leading tokens dominate the distribution. At high temperature, sampling spreads across many tokens, so lower-ranked continuations become more common. For code generation, that extra diversity can reveal alternative implementations, but it also raises the risk of syntactic or logical errors.
Implementing a Simple Self-Repair Loop
A self-repair loop generates code, tests it, and iteratively fixes any errors. We demonstrate the core logic using a synthetic problem where the "model" has a known error rate that decreases with each repair attempt.
def simulate_repair_round(attempt, base_pass_rate=0.45, seed_offset=0):
"""
Simulate a model generation/repair attempt.
Each round has a higher probability of success, modeling
the improvement that execution feedback provides.
"""
np.random.seed(attempt * 17 + 42 + seed_offset)
repair_bonus = attempt * 0.18
success_prob = min(0.95, base_pass_rate + repair_bonus)
success = np.random.random() < success_prob
if success:
feedback = "All tests passed."
else:
errors = [
"AssertionError: expected 2.5, got 2",
"TypeError: comparison not supported between str and int",
"IndexError: list index out of range",
"ValueError: median of empty sequence",
]
feedback = f"Test failed: {errors[attempt % len(errors)]}"
return success, feedbackSelf-Repair Loop Simulation ======================================== Problem 1: Round 1: FAIL | Test failed: AssertionError: expected 2.5, got 2 Round 2: FAIL | Test failed: TypeError: comparison not supported betwee Round 3: PASS | All tests passed. Problem 2: Round 1: FAIL | Test failed: AssertionError: expected 2.5, got 2 Round 2: FAIL | Test failed: TypeError: comparison not supported betwee Round 3: PASS | All tests passed. Problem 3: Round 1: FAIL | Test failed: AssertionError: expected 2.5, got 2 Round 2: PASS | All tests passed.

The stylized example illustrates the pattern reported in self-repair systems: the first repair can fix many obvious failures, while later attempts operate on a progressively harder residual set. The exact gains depend on the model, tests, and feedback quality; the plotted rates are illustrative rather than benchmark measurements.
Docstring Quality and Generation Success
The quality of the natural language specification has a measurable effect on generation quality. We can simulate this by scoring docstrings for specificity and showing the relationship between docstring quality scores and generation success rates.
def score_docstring_quality(docstring):
"""
Score a docstring's specificity and completeness.
Returns a value in [0, 1], where higher means more specific.
"""
score = 0.0
max_score = 6.0
words = docstring.lower().split()
n_words = len(words)
# Length bonus
if n_words > 10:
score += 1.0
elif n_words > 5:
score += 0.5
# Mentions return behavior
if any(w in words for w in ["return", "returns", "output", "result"]):
score += 1.0
# Mentions edge cases
if any(
w in words
for w in ["empty", "none", "null", "zero", "negative", "duplicate"]
):
score += 1.0
# Mentions types or constraints
if any(
w in words
for w in ["list", "string", "int", "float", "integer", "sorted"]
):
score += 1.0
# Includes example phrasing
if any(w in words for w in ["example", "e.g.", "given", "for"]):
score += 1.0
# Specifies boundary conditions
if any(
w in words for w in ["even", "odd", "average", "both", "two", "middle"]
):
score += 1.0
return score / max_score
The positive trend supports the practical intuition that more specific docstrings help, while the scatter shows that specification quality is not the only factor. Clear edge cases, return behavior, and type constraints reduce ambiguity and can lower iteration costs downstream.
Key Parameters
The key parameters for code generation pipelines are:
- temperature: Controls output diversity. Values of 0.2 to 0.5 work well for code, balancing correctness with some variation for best-of-N selection.
- top_p (nucleus sampling threshold): Typical values of 0.90 to 0.95 keep high-probability tokens while allowing occasional exploration.
- n (candidates per problem): More candidates dramatically improve pass@k. Even n=5 roughly doubles solve rates on moderately difficult problems compared to n=1.
- max_tokens: Set large enough to avoid truncating function bodies. For typical function implementations, 256 to 512 tokens is usually sufficient.
- repair iterations: One or two repair rounds provide most of the gain; three or more have diminishing returns.