A 25-Step TDD Kata: Building a Markdown Renderer in Rust
On this page2 sections ▾
There's a gap between knowing a language's syntax and actually feeling comfortable in it. For me, that gap is currently Rust. I've been looking for challenges that push me to write code in a structured way and get faster with my IDE shortcuts - not just toy examples, but something with enough surface area to feel real.
That search brought me to code katas. Inspired by classics like the String Calculator by Roy Osherove, I put together a more substantial challenge: a 25-step kata to build a Markdown-to-HTML renderer from scratch.
I'm working through this in Rust, but the kata is completely language-agnostic. TDD works the same way whether you're in C#, Python, TypeScript, or anything else. So here's the challenge: pick your language and leave a comment below letting us know which one you used. I'd love to see solutions in different languages stack up.
The structure throughout is the TDD cycle of Red-Green-Refactor.

The goal is to embrace the TDD rhythm: write a failing test (Red), make it pass with the simplest code possible (Green), then clean it up (Refactor). Resist the urge to skip ahead.
#The Challenge
Your task is to create a function that takes a string of Markdown and returns the correctly formatted HTML string. Build up the functionality one feature at a time, keeping the test suite green before moving to the next step.
#The 25 Progressions
Here is the step-by-step path to build your renderer. Each step represents a new test (or set of tests) you should write first.
#Phase 1: The Document Structure
-
Handle Empty Input
- Input:
"" - Output:
""
- Input:
-
A Single Line of Text
- Input:
Hello, World! - Output:
<p>Hello, World!</p>
- Input:
-
Multiple Lines in One Paragraph
- Input:
Hello,\nWorld! - Output:
<p>Hello, World!</p>
- Input:
-
Separate Paragraphs
- Input:
Line 1\n\nLine 2 - Output:
<p>Line 1</p><p>Line 2</p>
- Input:
#Phase 2: Headers
-
Header 1
- Input:
# Header 1 - Output:
<h1>Header 1</h1>
- Input:
-
Header 2
- Input:
## Header 2 - Output:
<h2>Header 2</h2>
- Input:
-
Support all Headers (H3-H6)
- Test inputs from
### H3through###### H6.
- Test inputs from
-
Refactoring Break
- This is a perfect time to refactor. Instead of multiple
ifconditions, can you create a single, elegant piece of logic that counts the#characters to determine the header level?
- This is a perfect time to refactor. Instead of multiple
#Phase 3: Inline Styling
-
Emphasis (Italics)
- Input:
This is *italic* text. - Output:
<p>This is <em>italic</em> text.</p>
- Input:
-
Alternative Emphasis
- Input:
This is _italic_ text. - Output:
<p>This is <em>italic</em> text.</p>
- Input:
-
Strong (Bold)
- Input:
This is **bold** text. - Output:
<p>This is <strong>bold</strong> text.</p>
- Input:
-
Alternative Strong
- Input:
This is __bold__ text. - Output:
<p>This is <strong>bold</strong> text.</p>
- Input:
-
Strikethrough
- Input:
This is ~~struck~~ text. - Output:
<p>This is <del>struck</del> text.</p>
- Input:
-
Nested Inline Styles
- Input:
This is **bold and *italic*** text. - Output:
<p>This is <strong>bold and <em>italic</em></strong> text.</p>
- Input:
#Phase 4: Links and Images
-
Links
- Input:
[Gordon's Blog](https://gordonbeeming.com) - Output:
<p><a href="https://gordonbeeming.com">Gordon's Blog</a></p>
- Input:
-
Images
- Input:
 - Output:
<p><img src="image.jpg" alt="An alt text"></p>
- Input:
#Phase 5: Lists and Blocks
-
Unordered List (Single Item)
- Input:
* List item - Output:
<ul><li>List item</li></ul>
- Input:
-
Unordered List (Multiple Items)
- Input:
* Item 1\n* Item 2 - Output:
<ul><li>Item 1</li><li>Item 2</li></ul>
- Input:
-
Ordered List
- Input:
1. First item\n2. Second item - Output:
<ol><li>First item</li><li>Second item</li></ol>
- Input:
-
Blockquotes
- Input:
> This is a quote. - Output:
<blockquote><p>This is a quote.</p></blockquote>
- Input:
-
Horizontal Rule
- Input:
--- - Output:
<hr>
- Input:
#Phase 6: Advanced Elements & Final Polish
-
Inline Code
- Input:
Use the `let` keyword. - Output:
<p>Use the <code>let</code> keyword.</p>
- Input:
-
Security Refactor (HTML Escaping)
- Input:
<script> - Output:
<p><script></p>
- Input:
-
Fenced Code Blocks
- Input:
"```\nlet x = 5;\n```" - Output:
<pre><code>let x = 5;\n</code></pre>
- Input:
-
Links with Titles
- Input:
[Tiani's Site](https://tianibeeming.com \"Tiani Beeming\") - Output:
<p><a href="https://tianibeeming.com" title="Tiani Beeming">Tiani's Site</a></p>
- Input:
After 25 steps, you'll have a renderer capable of turning complex Markdown into clean HTML.
For example, a piece of Markdown like this:
# My Document
This paragraph contains **bold text** and a link to [my wife Tiani's blog](https://tianibeeming.com).
- List Item 1
- List Item 2...will produce the following HTML output.
<h1>My Document</h1>
<p>This paragraph contains <strong>bold text</strong> and a link to <a href="https://tianibeeming.com">my wife Tiani's blog</a>.</p>
<ul>
<li>List Item 1</li>
<li>List Item 2</li>
</ul>That's the full 25-step progression. By the time you're done, you'll have a functional Markdown renderer and dozens of reps through the TDD cycle. The process starts to feel natural somewhere around step 10 - that's when it clicks.
Drop a comment below with the language you used and how it went.