The Yahtzee Engine

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

Domain
Python
Difficulty
hard
Seniority
L4
Asked in
Optimove

Problem

Given 5 dice, return a dict with scores for all Yahtzee categories: 'ones' through 'sixes' (sum of matching dice), 'three_of_a_kind' (sum of all if >=3 match else 0), 'four_of_a_kind', 'full_house' (25 if 3+2 pattern), 'small_straight' (30 if 4 consecutive), 'large_straight' (40 if 5 consecutive), 'yahtzee' (50 if all 5 match), 'chance' (sum of all).

Five dice. Six faces. Score it.

Example 1

Input:

dice = [1,1,1,2,3]

Output:

{"ones":3,"twos":2,"threes":3,"fours":0,"fives":0,"sixes":0,"three_of_a_kind":8,"four_of_a_kind":0,"full_house":0,"small_straight":0,"large_straight":0,"yahtzee":0,"chance":8}

Example 2

Input:

dice = [1,2,3,4,5]

Output:

{"ones":1,"twos":2,"threes":3,"fours":4,"fives":5,"sixes":0,"three_of_a_kind":0,"four_of_a_kind":0,"full_house":0,"small_straight":30,"large_straight":40,"yahtzee":0,"chance":15}

Worked solution and explanation

Why this problem exists in real interviews

Strip the game away and this is thirteen predicates all reading one shared face-count profile: how many dice show each face, the shape of those counts, and which faces are present at all. Anyone can sum five dice. What separates candidates is reading 'at least' as >= rather than ==, testing full house on the whole count profile rather than the biggest count, and finding straights by presence rather than by positions in a sorted list. Get any one wrong and a specific hand breaks: five identical dice become a full house, four fours lose their three-of-a-kind score, or the repeated 2 in 1, 2, 2, 3, 4 erases a small straight.


Break down the requirements

Step 1: Count die face frequencies

Build a face-count dict from the five dice with freq.get(d, 0) + 1. Almost every category downstream reads this one table instead of rescanning the raw list.

Step 2: Score the upper section (ones through sixes)

For ones through sixes, score face * freq.get(face, 0): only the dice matching that face contribute.

Step 3: Score n-of-a-kind, full house, and yahtzee

three_of_a_kind and four_of_a_kind pay the whole-hand total when max(freq.values()) clears the threshold, else 0. full_house needs a 3-and-2 split, so 2 in counts and 3 in counts. yahtzee needs max_freq == 5. Note that five-of-a-kind is a yahtzee, never a full_house.

Step 4: Detect the straights

A small_straight is any run of 4 consecutive faces, a large_straight a run of 5. Test each candidate run by asking whether every face in it is present in the frequency dict: membership is what matters, not order, so no sort is needed and a repeated face cannot shift the run out of place.

Step 5: Score chance

chance is just sum(dice), regardless of pattern.


The solution

Frequency-driven categorical scoring engine
def yahtzee_scores(dice: list) -> dict:
    freq = {}
    for d in dice:
        freq[d] = freq.get(d, 0) + 1
    total = sum(dice)
    max_freq = max(freq.values())

    scores = {}
    names = ["ones", "twos", "threes", "fours", "fives", "sixes"]
    for i in range(6):
        face = i + 1
        scores[names[i]] = face * freq.get(face, 0)

    scores["three_of_a_kind"] = total if max_freq >= 3 else 0
    scores["four_of_a_kind"] = total if max_freq >= 4 else 0

    counts = list(freq.values())
    scores["full_house"] = 25 if (2 in counts and 3 in counts) else 0

    has_small = False
    for start in [1, 2, 3]:
        if all(v in freq for v in range(start, start + 4)):
            has_small = True
            break
    scores["small_straight"] = 30 if has_small else 0

    has_large = False
    for start in [1, 2]:
        if all(v in freq for v in range(start, start + 5)):
            has_large = True
            break
    scores["large_straight"] = 40 if has_large else 0

    scores["yahtzee"] = 50 if max_freq == 5 else 0
    scores["chance"] = total
    return scores

Common follow-up questions