Kata Potter
Level: Advanced 60–90 minConcepts: 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 Set | Discount |
|---|---|
| 1 book | 0% (€8) |
| 2 different books | 5% (€15.20) |
| 3 different books | 10% (€21.60) |
| 4 different books | 20% (€25.60) |
| 5 different books | 25% (€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
| Basket | Expected 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.
- C# (.NET 8, xUnit, FluentAssertions 6.12.0) — walkthrough
- TypeScript (Node 20, Vitest 1.6, TS 5 strict) — walkthrough
- Python (3.11, pytest) — walkthrough
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.
Related reading
- Examples Pin Intent. Properties Pin the Invariants.
Example-based tests anchor scenarios in the team's vocabulary. Property-based tests anchor invariants across the input space. The agent needs both axes, and most suites only carry one. - Katas Are Rehearsal, Not Performance
You don't practice TDD on production code. You practice on katas and bring the muscle memory to production. The gap between knowing TDD and doing TDD is reps. - The Test Pyramid Was an Economic Argument
The test pyramid was not a quality law. It was a cost structure: unit tests were cheap, integration tests were expensive, so you wrote many of the first and few of the second. Agents collapsed the cost of writing tests at every level, and the cheapest test that still tells the truth is the one that pins a seam the agent cannot fake.