Kata Potter

Level: Advanced 60–90 min

Concepts: AlgorithmsBusiness LogicEdge CasesIncremental Design

Solutions: C# | TypeScript | Python


A bookshop sells five different books in a series at €8 each. Buying multiple unique titles earns a discount. Your job: calculate the cheapest price for any basket of books.

Pricing Rules

Unique Titles in SetDiscount
1 book0% (€8)
2 different books5% (€15.20)
3 different books10% (€21.60)
4 different books20% (€25.60)
5 different books25% (€30.00)

Books are identified as Book 1 through Book 5.

The Catch

When a basket contains multiple copies, you must group them into sets that minimize total cost. The greedy approach (always making the biggest set) doesn’t always work.

Example

Basket: 2× Book 1, 2× Book 2, 2× Book 3, 1× Book 4, 1× Book 5

Greedy grouping (wrong):

  • Set of 5: (1,2,3,4,5) = 5 × €8 × 0.75 = €30.00
  • Set of 3: (1,2,3) = 3 × €8 × 0.90 = €21.60
  • Total: €51.60

Optimal grouping (correct):

  • Set of 4: (1,2,3,4) = 4 × €8 × 0.80 = €25.60
  • Set of 4: (1,2,3,5) = 4 × €8 × 0.80 = €25.60
  • Total: €51.20

Two sets of 4 is cheaper than a set of 5 plus a set of 3.

Test Cases

BasketExpected Price
(empty)€0.00
1× Book 1€8.00
2× Book 1€16.00
1× Book 1, 1× Book 2€15.20
1× Book 1, 1× Book 2, 1× Book 3€21.60
1× Book 1, 1× Book 2, 1× Book 3, 1× Book 4€25.60
1× each of all 5€30.00
2× Book 1, 1× Book 2€23.20 (€15.20 + €8.00)
2× each of Books 1–5€60.00 (2 sets of 5)
2×(1,2,3), 1×(4,5)€51.20 (2 sets of 4, not 5+3)

The last test case is the critical one — it proves your algorithm handles optimization correctly.

Bonus

  • Generalize to N books with configurable discount tiers
  • Return the grouping breakdown alongside the price
  • Handle large baskets efficiently (100+ books)

Hint

Start with the simple cases — single books, single sets of unique titles. The algorithm gets interesting when you have duplicates. One approach: generate all possible groupings and pick the cheapest. A smarter approach: recognize that splitting a set of 5 + set of 3 into two sets of 4 always saves money, and apply that transformation.

Reference Walkthrough

Reference implementations in C#, TypeScript, and Python live at tddbuddy-reference-katas/kata-potter. This is an F2 (light builder) kata: one primary entity (Basket), a static PricingRules module holding the base price (€8.00) and the discount table (1→0%, 2→5%, 3→10%, 4→20%, 5→25%), a single BookOutOfRangeError for malformed book ids, and one small test-folder BasketBuilder (20–30 lines) whose chained .withBook(series, count) calls read as the basket literal — “two of book 1, two of book 2, two of book 3, one of book 4, one of book 5”.

The greedy-fails scenario is the whole point. A pure greedy solver processes [2, 2, 2, 1, 1] by pulling the largest set of distinct titles first — a 5-set (€30.00) plus a 3-set (€21.60) for €51.60. The optimal grouping is two 4-sets (€25.60 each) for €51.20. The reference implementation uses a greedy-then-adjust algorithm: build the greedy histogram, then swap every (5-set, 3-set) pair for two 4-sets. That single local swap is provably the only improvement available under the fixed five-title discount table (every other adjacent swap is neutral or worsening), so it suffices. The design rationale — why not full DP, why a histogram, why named constants for the discount table — lives in the C# walkthrough.

Scope note — pure domain only. The reference covers basket pricing with the fixed five-title series. Arbitrary series sizes with a configurable discount table, returning the grouping breakdown alongside the price (useful for receipts), and large-basket performance work are listed as stretch goals in the repo README — those introduce a PricingRules collaborator and tip the kata into F3.

This kata ships in Agent Full-Bake mode at middle gear, the F2 (light builder) tier. See the repo’s Gears section for why middle gear is the deliberate choice.