Multi-Threaded Santa
Level: Advanced 60–90 minConcepts: ConcurrencyDesign PatternsBoundariesEdge Cases
Solutions: C# | TypeScript | Python
Make, wrap, load, and deliver 1 million presents on Christmas Eve — using the fewest elves (threads) and the least time possible.
Requirements
Pipeline
Presents flow through four stages, each running as a separate process/thread pool:
- Making → Made queue (max 1,000)
- Wrapping → Wrapped queue (max 2,000)
- Loading → Loaded queue (max 5,000)
- Delivering → Delivered (unlimited)
Each present must pass through all four stages in order.
Work Timing
Each operation on a Present takes simulated time:
present.make()→ 50mspresent.wrap()→ 10mspresent.load()→ 5mspresent.deliver()→ via sleigh (see below)
Delivery Constraint
- Only one delivery worker (Santa’s sleigh)
- Each delivery round takes 500ms, regardless of how many presents are on the sleigh
- No presents can be loaded while the sleigh is delivering
Elf Pool
- Worker threads (“elves”) are drawn from a shared Elf Pool
- Once assigned, an elf cannot be terminated — only reassigned
- Elves can move between stages via the pool
Cost Calculation
- 1 elf working for 1 second = 1 cookie
- Total cost = number of elves × total time in seconds
- Goal: minimize total cookie expenditure
Failure Rules
- If any worker process fails, the clock continues (costs accumulate)
- If the Elf Pool fails, the challenge is forfeited
Test Cases
| Scenario | Expected |
|---|---|
| Make 1 present | Present exists in Made queue |
| Made queue full (1,000) | Making blocks until space available |
| Wrap 1 present from Made queue | Present moves to Wrapped queue |
| Load while sleigh delivering | Loading blocks |
| Deliver 1 round | All loaded presents move to Delivered |
| Elf reassignment | Elf moves from Making to Wrapping |
| Full pipeline, 10 presents | All 10 delivered in correct order |
Bonus
- Implement auto-scaling — dynamically adjust elf allocation based on queue depths
- Add monitoring — report queue sizes, throughput per stage, and elf utilization in real-time
- Compare strategies: fixed allocation vs dynamic rebalancing
- Find the optimal elf distribution for minimum cost
Hint
Start without concurrency. Build the pipeline with a single thread processing one present through all four stages. Then add queues with size limits. Then add multiple workers per stage. Concurrency bugs are much easier to diagnose when the sequential logic is already proven correct.
Reference Walkthrough
Full C#, TypeScript, and Python implementations live at tddbuddy-reference-katas/multi-threaded-santa with nineteen scenarios across all three languages: present lifecycle, bounded queue behavior, sequential pipeline, concurrent pipeline, and cost calculation.
- C# (.NET 8, xUnit, FluentAssertions) — uses
Channel<T>for bounded queues andSemaphoreSlimfor sleigh exclusion. Walkthrough - TypeScript (Node 20, Vitest, strict types) — async pipeline with Promise-based bounded queues. Node.js is single-threaded, so “concurrency” is cooperative via the event loop. Walkthrough
- Python (3.11, pytest, threading) — real OS threads with
queue.Queueandthreading.Lock. The GIL means threads interleave at I/O boundaries. Walkthrough
The key teaching point: build the sequential pipeline first, prove correctness without threads, then layer concurrency on top. Tests assert on outcomes (all presents delivered, correct counts), never on timing.
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. - 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. - Agents Amplify Whatever Vocabulary They Find
Agents do not bring vocabulary to your codebase. They mirror what's already there. Strong vocabulary gets agent contributions that read like the team. Weak vocabulary gets faster mediocrity. The work you did for craft reasons just became leverage.