Abstract

Recall@k is the fraction of a query’s relevant results that appear in its top k, averaged across queries. It is often confused with Hit@k. Hit@k asks whether any relevant result was found, while Recall@k asks how many of them were. The two are equal only when every query has exactly one relevant result.

Cue

  • What does Recall@k measure? → The share of each query’s relevant results found in its top k
  • How does it differ from Hit@k? → Hit@k scores 1 for any hit, while Recall@k gives partial credit per relevant item
  • When are they equal? → When each query has exactly one relevant item
  • What’s its upper bound when there are more relevant items than k? → k / (number of relevant items), not 1

Definition

For a set of queries , where is the set of relevant results for and is its top k retrieved:

This is the macro average, where every query counts equally. The micro average pools all items first, , so queries with many relevant items count for more. Say which one you report.

Worked example

Three questions, some with more than one expected element:

QuestionRelevant itemsFound in top 4Recall@4Hit@4
Q1210.501
Q2111.001
Q3300.000
Mean0.500.67

Hit@4 gives Q1 full marks because one of its two elements was found. Recall@4 gives it half. On Q2, which has one relevant item, the two metrics agree.

Implementation (Java)

record Retrieval(List<String> rankedIds, Set<String> relevantIds) {}
 
static double recallAtK(List<Retrieval> runs, int k) {
    // Unanswerable questions have no relevant items, so recall is undefined
    var answerable = runs.stream()
            .filter(r -> !r.relevantIds().isEmpty())
            .toList();
    if (answerable.isEmpty()) return 0.0;
 
    double total = 0.0;
    for (var r : answerable) {
        long found = r.rankedIds().stream()
                .limit(k)
                .distinct()   // a duplicate result shouldn't count twice
                .filter(r.relevantIds()::contains)
                .count();
        total += (double) found / r.relevantIds().size();
    }
    return total / answerable.size();   // macro average
}

Time is .

Why 4?

k = 4 because that is how many chunks QuestionAnswerAdvisor puts in the prompt. It is Spring AI’s SearchRequest default. Why 4 is a common choice in general (screen layouts, LLM context windows, and human attention) is covered in Hit@4 → Why 4?.

A small k puts a ceiling on recall. A question whose answer is spread over six chunks can score at most 4/6 = 0.67 at k = 4, even with perfect ranking. Check how many relevant items each question has before reading Recall@4 as a judgement on ranking quality.

Why Episode 3 reports Hit@4, not Recall@4

The post calls Hit@4 “often called recall at k”. The two terms do get used loosely, but in Episode 3 the metric is Hit@4: a question with two expected elements “counts as found if either one is”. That is the right choice there, because:

  • Most golden-set questions point at one requirement, so on those questions the two metrics give the same number.
  • For a question with two acceptable sources, either one is enough to answer it. Recall would penalise retrieval for not fetching both.

Recall@k is the better metric when a complete answer needs every relevant piece, such as “list every requirement that mentions citations”, or when evaluating a first-stage retriever whose results a re-ranker will narrow down.

Strengths and blind spots

✅ Credits completenessRewards fetching every piece of a multi-part answer.
✅ Standard for retriever evaluationThe usual metric for first-stage retrieval before re-ranking.
❌ Ignores rankRank 1 and rank k count the same. Pair it with MRR or nDCG.
❌ Ceiling below 1 when there are more relevant items than kScores depend on how many relevant items a question has, not just on retrieval quality.
❌ Needs complete labelsEvery relevant item must be in the golden set. A missed label lowers recall unfairly.

BLUESKY — START THE THREAD KO-FI / RSS