Roman Numerals
Level: Beginner 15–30 minConcepts: AlgorithmsNumbers
Solutions: C# | TypeScript | Python
Create a converter that turns a positive integer (1–3999) into its Roman numeral string.
Requirements
- Convert Arabic numbers to Roman numerals in the range 1 to 3999.
- Follow standard Roman numeral rules:
- I = 1, V = 5, X = 10, L = 50, C = 100, D = 500, M = 1000
- When a smaller value precedes a larger value, subtract the smaller (e.g., IV = 4, IX = 9, XL = 40, XC = 90, CD = 400, CM = 900)
- Only the six subtractive pairs above appear; no IL, IC, IM, etc.
Test Cases
| Arabic Number | Roman Numeral | Notes |
|---|---|---|
| 1 | I | Basic conversion |
| 4 | IV | Subtractive — gear-shift moment |
| 9 | IX | Subtractive |
| 40 | XL | Subtractive at tens |
| 90 | XC | Subtractive at tens |
| 400 | CD | Subtractive at hundreds |
| 900 | CM | Subtractive at hundreds |
| 1984 | MCMLXXXIV | Composite across four orders of magnitude |
| 3999 | MMMCMXCIX | Maximum valid number |
Stretch Goals (Not in the Reference)
The reference implementations cover Arabic → Roman conversion for the nine scenarios above and stop. If you want to extend further:
- Roman → Arabic (the reverse direction)
- Input validation — rejecting numbers less than 1 or greater than 3999, non-integers, etc.
- Roman-input validation — invalid characters, repeated symbols beyond three (e.g., “IIII”, “VV”), invalid subtractive combinations (e.g., “IL”, “IC”)
The canonical TDD teaching arc for this kata is Arabic → Roman only — that is the direction where the subtractives-in-the-table insight lands cleanly. The reverse direction and validation each deserve their own kata session.
Tips
- Start with the simplest conversions first (1, 2, 3).
- Notice when
4arrives that “concatenate I n times” is a dead end — the table of(value, symbol)pairs with subtractives baked in is the move. - Add larger values gradually (10, 40, 100, 400, 1000) — the subtractives (9, 90, 900) should pass on arrival once the table has them.
Reference Walkthrough
Full C#, TypeScript, and Python implementations live at tddbuddy-reference-katas/roman-numerals — the same nine scenarios across all three languages, walked commit-by-commit through the TDD cycle.
- C# (.NET 8, xUnit, FluentAssertions) — walkthrough
- TypeScript (Node 20, Vitest, strict types) — walkthrough
- Python (3.11, pytest) — walkthrough
The reference covers Arabic → Roman only — the reverse direction and input validation are stretch goals and are not implemented there.
This is a Pedagogy mode kata — the walkthroughs step through the TDD cycle commit-by-commit. Watch the gear shift from low (fake-it, hardcoded branches, input-keyed dictionary) to middle once the 4 → IV scenario forces the refactor. The teaching point is the table that beats special cases: triangulating 1 → I, 2 → II, 3 → III tempts you into “repeat I n times”, and 4 breaks it flat. The right move is to promote the lookup from {input → output} to an ordered list of (value, symbol) pairs with subtractives baked in as first-class entries — [(1000,"M"), (900,"CM"), (500,"D"), (400,"CD"), (100,"C"), (90,"XC"), (50,"L"), (40,"XL"), (10,"X"), (9,"IX"), (5,"V"), (4,"IV"), (1,"I")] — and a greedy subtract loop. The payoff is the chain of five spec — commits at the end: 9, 90, 900, 1984, and 3999 all pass on arrival once the table is complete. Subtractives are not exceptions to the rule; they are entries in the table. See the repo’s Gears section for why middle gear is where the table lands.