Gilded Rose
Level: Advanced 60–90 minConcepts: RefactoringEdge CasesLegacy Code
Solutions: C# | TypeScript | Python
You have inherited the Gilded Rose inn’s inventory system. The code works but is a tangled mess of nested conditionals. Your job: add support for a new item type without breaking existing behavior.
This kata is different from most — you start with existing code, not a blank slate. The first step is understanding what the code does, not writing new features.
Existing Behavior
The system updates inventory daily. Each item has:
name— the item namesellIn— days remaining to sell the itemquality— how valuable the item is (0–50)
At the end of each day, sellIn and quality decrease by 1.
Special rules:
- Once the sell-by date passes (
sellIn < 0),qualitydegrades twice as fast qualityis never negative- “Aged Brie” increases in quality the older it gets
- “Sulfuras, Hand of Ragnaros” is legendary — never sold, never decreases in quality, quality is always 80
- “Backstage passes” increase in quality as the concert approaches:
- Quality increases by 2 when there are 10 days or less
- Quality increases by 3 when there are 5 days or less
- Quality drops to 0 after the concert (
sellIn < 0)
qualitynever exceeds 50 (except Sulfuras which is always 80)
The Legacy Code
function updateQuality(items) {
for (var i = 0; i < items.length; i++) {
if (items[i].name != 'Aged Brie' && items[i].name != 'Backstage passes to a TAFKAL80ETC concert') {
if (items[i].quality > 0) {
if (items[i].name != 'Sulfuras, Hand of Ragnaros') {
items[i].quality = items[i].quality - 1
}
}
} else {
if (items[i].quality < 50) {
items[i].quality = items[i].quality + 1
if (items[i].name == 'Backstage passes to a TAFKAL80ETC concert') {
if (items[i].sellIn < 11) {
if (items[i].quality < 50) {
items[i].quality = items[i].quality + 1
}
}
if (items[i].sellIn < 6) {
if (items[i].quality < 50) {
items[i].quality = items[i].quality + 1
}
}
}
}
}
if (items[i].name != 'Sulfuras, Hand of Ragnaros') {
items[i].sellIn = items[i].sellIn - 1
}
if (items[i].sellIn < 0) {
if (items[i].name != 'Aged Brie') {
if (items[i].name != 'Backstage passes to a TAFKAL80ETC concert') {
if (items[i].quality > 0) {
if (items[i].name != 'Sulfuras, Hand of Ragnaros') {
items[i].quality = items[i].quality - 1
}
}
} else {
items[i].quality = 0
}
} else {
if (items[i].quality < 50) {
items[i].quality = items[i].quality + 1
}
}
}
}
}
Your Task
Step 1: Write characterization tests. Before changing anything, write tests that capture the current behavior for all item types. These are your safety net.
Step 2: Refactor. Clean up the nested conditionals. Make the code readable. Your characterization tests should stay green throughout.
Step 3: Add “Conjured” items. Conjured items degrade in quality twice as fast as normal items.
Test Cases
| Item | SellIn | Quality | Next Day SellIn | Next Day Quality |
|---|---|---|---|---|
| Normal Item | 10 | 20 | 9 | 19 |
| Normal Item | 0 | 20 | -1 | 18 |
| Normal Item | 5 | 0 | 4 | 0 |
| Aged Brie | 10 | 20 | 9 | 21 |
| Aged Brie | 0 | 20 | -1 | 22 |
| Aged Brie | 5 | 50 | 4 | 50 |
| Sulfuras | 10 | 80 | 10 | 80 |
| Backstage passes | 15 | 20 | 14 | 21 |
| Backstage passes | 10 | 20 | 9 | 22 |
| Backstage passes | 5 | 20 | 4 | 23 |
| Backstage passes | 0 | 20 | -1 | 0 |
| Conjured Item | 10 | 20 | 9 | 18 |
| Conjured Item | 0 | 20 | -1 | 16 |
Hint
Start with characterization tests. Run the existing code with known inputs and assert the actual outputs — even if the behavior seems wrong. Your job is to capture reality, not fix it. Once you have a full safety net, refactoring becomes mechanical.
Reference Walkthrough
A full commit-by-commit TDD tour of this kata lives at tddbuddy-reference-katas. Each scenario from the shared SCENARIOS.md is driven red → green in a single commit, so git log reads as a teaching narrative.
The same scenarios land across three languages using shared ubiquitous vocabulary (GildedRoseInn, Inventory, Item, Category) and a fluent ItemBuilder test data builder:
- C# — .NET 8, xUnit, FluentAssertions → walkthrough
- TypeScript — Node 20, Vitest, strict types → walkthrough
- Python — Python 3.11, pytest, dataclasses → walkthrough
Each walkthrough maps every scenario to the exact commit that introduced its test and its minimal passing implementation.
Related reading
- 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. - The Hidden Output of TDD Was Never Code
The visible artifact of TDD is a passing test suite. The actual artifact, the one that compounded value over the life of the codebase, was the vocabulary the team built while writing it. The dictionary, not the regression net. - 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.