Gilded Rose

Level: Advanced 60–90 min

Concepts: 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 name
  • sellIn — days remaining to sell the item
  • quality — how valuable the item is (0–50)

At the end of each day, sellIn and quality decrease by 1.

Special rules:

  1. Once the sell-by date passes (sellIn < 0), quality degrades twice as fast
  2. quality is never negative
  3. “Aged Brie” increases in quality the older it gets
  4. “Sulfuras, Hand of Ragnaros” is legendary — never sold, never decreases in quality, quality is always 80
  5. “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)
  6. quality never 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

ItemSellInQualityNext Day SellInNext Day Quality
Normal Item1020919
Normal Item020-118
Normal Item5040
Aged Brie1020921
Aged Brie020-122
Aged Brie550450
Sulfuras10801080
Backstage passes15201421
Backstage passes1020922
Backstage passes520423
Backstage passes020-10
Conjured Item1020918
Conjured Item020-116

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.


  • 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.