Tutorials 17 min read

Markdown Ate the Underscores in __init__: The Everyday Text That Turns Into Formatting, and the Backslash That Stops It

MMNMNOTE
markdownmarkdown escapingbackslash escapemarkdown formattingmarkdown syntax__init__snake_casedunder

When Markdown sees two underscores flanking a word, asterisks wrapped around digits, or a pound sign alone at a line's start, it turns that text into formatting — not content. A backslash placed immediately before any of those characters tells CommonMark-compatible renderers to treat the character as literal. Inside code spans, the backslash is not enough: change the delimiter instead.

Why Markdown Eats Ordinary Text

Seven Stack Overflow questions on the escape, backslash, and underscore axis have accumulated 385,297 combined views as of November 2026 — the top question ("how to show underscores symbol in markdown?") alone has 177,278 views.1 That demand is structural, not accidental.

The underlying cause: Markdown was designed for prose. Its author, John Gruber, envisioned users wrapping *asterisks* around words they wanted emphasized and typing # to signal headings.2 The format was not designed with software identifiers in mind. A 2024 study of 31,169,429 Stack Overflow answers found that 38.5% of answers contain code formatting, and in 63.5% of those cases the code formatting is used to highlight identifiers rather than runnable code.3 Code notation became the dominant practical use of Markdown escaping — not Gruber's prose emphasis.

The collision point: underscores. A study of 135 developers (Binkley et al., ICPC 2009) found that underscores and camelCase are the two most common conventions for multi-word identifiers.4 Write __init__, my_var, or obj.__dict__ in a plain note and Markdown interprets the underscores as emphasis delimiters.

The CommonMark specification extended Markdown's escaping to cover any ASCII punctuation character.5 But three major renderers — Python-Markdown, pandoc's markdown_strict reader, and the original Markdown.pl — still recognize only Gruber's original list of 15–16 characters. And pandoc's default markdown reader adds a trap of its own: a backslash before an uppercase letter may be parsed as a raw TeX command and silently deleted from HTML output. The behavior is engine-dependent, and the safe path depends on which renderer processes your notes.

The Trap Table: Everyday Text That Becomes Formatting

The table below covers ten tested setups: commonmark.js 0.31.2, markdown-it 14.1.0 (CommonMark and GFM modes), micromark 4.0.1 with GFM extension, Python-Markdown 3.11.3 (default), and pandoc 3.13 (four readers: markdown, gfm, markdown_strict, commonmark), plus Markdown.pl 1.0.1.6 "Universal fix" means it works in all ten. A code span wraps text in backticks and produces <code> output — the safest option when you need the text literal but want monospace rendering. Use \_ or \* escapes when you need the text in a prose font.

Text as typedWhat Markdown rendersWhy it firesUniversal fixEngine exceptions
__init__init (bold)__ = strong emphasis delimiter`__init__` (code span)pandoc markdown: literal — the intraword_underscores extension, enabled by default, suppresses mid-word double underscores
obj.__dict__obj.dict (bold)A period before __ is punctuation, enabling the left-flanking delimiter run even though __ is mid-identifier`obj.__dict__`pandoc markdown: literal (same extension); 9/10 tested setups render as bold
my_function_namemyfunctionname (italic)Two single underscores flanking a segment at a non-word boundary → italicmy\_function\_name or `my_function_name`CommonMark and GFM: literal mid-word by the intraword rule (§6.2); older engines may still italicize
2*3*4234 (the 3 italicized)A digit before * satisfies CommonMark's left-flanking delimiter condition; Example 356 shows 5*6*78 → 5<em>6</em>7852\*3\*4All ten tested setups render this as italic
#meeting notes (at line start)<h1>meeting notes</h1>ATX heading rule\#meeting notesCommonMark/GFM require a space after # — #tag is literal; Python-Markdown, pandoc markdown_strict, and Markdown.pl do not require the space and render #meeting notes as <h1>
C:\Users\name (Windows path)C: with the rest deletedpandoc markdown reader treats \U as a raw TeX inline command and strips it from HTML output`C:\Users\name` (code span)9/10 setups: rendered correctly as literal text; pandoc markdown only: silently deletes \Users\name — no warning
<branch-name>, <div>HTML passthrough or deletedMarkdown passes raw HTML to its writer\<branch-name\> or `<branch-name>`GFM/CommonMark: <branch-name> is not a valid HTML tag so it renders literally in some renderers; code span is the safe path everywhere
~~strikethrough~~ or ~subscript~Strikethrough (GFM) or subscript (pandoc)Tilde extension\~\~text\~\~Tilde behavior across GFM, Obsidian, and pandoc is covered in full by ["Write H₂O in a Note and GitHub Crosses Out the 2"][#531]7
1986. Some text or 1. item at a paragraph's startOrdered list starting at 1986Ordered-list marker1986\. Some textThe year-becomes-a-list trap and how list numbering works is covered by ["The Number You Type in a Markdown List Is Only a Suggestion"][#521]8
| in a GFM table cellSplits the columnGFM table pipe syntax| or `` `` ``

Note on \$, \~, \|, \", \=, \@, \<: In Python-Markdown (default), pandoc markdown_strict, and Markdown.pl, a backslash before these characters is not treated as an escape — the backslash appears literally in output. These characters are not in Gruber's original escapable list (see §Origins below). If your notes are processed by one of those engines, use a code span for portable safety.

Where Backslash Falls Short: Engine Exceptions

The CommonMark specification states: "Escaped characters are treated as regular characters and do not have their usual Markdown meanings."5 That rule holds cleanly in CommonMark and GFM renderers. Three common engines narrow it.

Python-Markdown, pandoc markdown_strict, and Markdown.pl recognize only the characters in Gruber's original list: \ `` * _ { } [ ] ( ) # + - . ! (Markdown.pl source L53 carries 16 entries; Python-Markdown 3.11.2 ESCAPED_CHARS adds >, also reaching 16).9 10 Write \$ in a MkDocs project (Python-Markdown engine) and the output contains a visible backslash before the dollar sign — the backslash was not consumed as an escape character.

Pandoc markdown (the default reader) adds a separate trap. Its documentation explains that inline TeX commands are preserved and passed unchanged to LaTeX and ConTeXt writers.11 In practice, this means C:\Users\name is parsed as C: followed by the TeX command \Users, which produces an empty string in HTML output. The path C:\Users\name becomes C: in the rendered page — with no warning, no error, no hint that anything was deleted. The same logic swallows Use \n for newlines → Use for newlines. A code span is the only protection: `C:\Users\name` passes through every tested renderer untouched.

The hashtag trap is engine-specific. CommonMark and GFM require a space after # for a heading — #meeting is not a heading in those renderers. Python-Markdown, pandoc markdown_strict, and Markdown.pl do not require the space: #meeting notes becomes <h1>meeting notes</h1>. RFC 7764, the IETF informational memo on Markdown variants, identifies "Multiple underscores in words" as the first documented difference between GitHub Flavored Markdown and original Markdown12 — but the no-space heading difference is equally consequential for anyone pasting hashtag-style labels into a renderer they did not choose.

Inside Code Spans, the Backslash Is Not Enough

Inside a code span or a fenced code block, CommonMark §2.4 is explicit: "Backslashes before other characters are treated as literal backslashes."5 A \* inside backticks stays as \* — the backslash is not consumed as an escape. To embed a backtick inside a code span, the solution is to change the delimiter to a longer backtick run, not to escape anything. The full mechanics — including unclosed-fence hazards and engine-specific edge cases — are covered by ["How to Put a Backtick Inside Code in Markdown"][#538].13

If you paste Markdown notes into Slack, Teams, or Telegram, escaping may behave differently from any renderer in the table above. Those apps implement their own dialect parsers. The divergence is covered by ["Paste a Markdown Note Into Slack, Teams, or Telegram and Your Italics Turn Bold"][#558].14

How the Escapable Character List Grew

John Gruber's original Markdown syntax page lists: \ `` * _ { } [ ] ( ) # + - . ! — fifteen character tokens, with backslash itself making a self-escape possible.2 The source of Markdown.pl 1.0.1 carries sixteen entries in its escape_table hash (> added).9

The CommonMark project, which began formalizing Markdown's rules in 2014, extended escaping to cover any ASCII punctuation character — a larger set, 32 characters in the ASCII punctuation block. That is the basis for the "any ASCII punctuation" language in GitHub's documentation: "You can tell GitHub to ignore (or escape) Markdown formatting by using \ before the Markdown character."15

CommonMark extended Gruber's list; it did not replace the core logic. A backslash before any character outside the extended set is still treated as a literal backslash — not discarded, not consumed. The full current list, with examples, is covered by [the MNMNOTE Markdown cheat sheet][#01] §Escape characters.16

Frequently Asked Questions

How do I write __init__ in Markdown without it turning bold?

Wrap it in backticks: `__init__`. That produces <code>__init__</code> — monospace, unambiguous, safe in every tested renderer. If you need the text in a prose font, escape both pairs of underscores: \_\_init\_\_. The backslash approach works in CommonMark, GFM, and pandoc, but the code span is portable everywhere.

Why is my Markdown backslash escape not working?

Three likely causes. First, your renderer may be Python-Markdown, pandoc markdown_strict, or Markdown.pl — those engines only recognize Gruber's original 15–16 characters, so \$ or \~ leaves a visible backslash in output. Second, you may be inside a code span or code block, where backslashes are literal by definition. Third, pandoc's default markdown reader may be treating your backslash as a TeX command prefix rather than an escape. A code span resolves all three cases.

How do I escape asterisks in Markdown?

Place a backslash immediately before each asterisk: 2\*3\*4 renders as 2*3*4. If the asterisks are part of a longer pattern you want preserved exactly — a glob, a pointer in pseudocode — a code span is cleaner: `2*3*4`. Both approaches work in CommonMark and GFM renderers.

How do I write a Windows file path in Markdown?

Use a code span: `C:\Users\name\Documents`. Do not rely on a backslash escape for Windows paths — pandoc's default markdown reader interprets \U, \D, and similar sequences as raw TeX inline commands and silently removes them from HTML output. The code span passes through all ten tested renderers unchanged.

How do I escape a hashtag at the start of a line in Markdown?

Prefix it with a backslash: \#meeting notes. In CommonMark and GFM, a # without a trailing space is not a heading, so \# is redundant but harmless. In Python-Markdown, pandoc markdown_strict, and Markdown.pl — which create headings from #tag without a space — \# is the escape that prevents the heading. Since # is in Gruber's original escapable list, \# works in all ten tested engines.

Does backslash escaping work inside a code block or fenced block?

No. The CommonMark specification states that inside code spans and code blocks, "Backslashes before other characters are treated as literal backslashes."5 The backslash appears in the output unchanged — it escapes nothing. For the technique of placing a backtick inside a code span, see ["How to Put a Backtick Inside Code in Markdown"][#538].13

What is the safest single rule to remember?

Use a code span for anything you want to survive every renderer verbatim — identifiers, paths, math expressions, shell syntax. Use a backslash escape for one-off inline prose fixes when you are on a CommonMark or GFM engine and you need the text in a prose font. Never rely on a backslash inside code spans or code blocks, and never rely on \$, \~, or other non-Gruber characters in Python-Markdown or markdown_strict environments.


The trap is not that Markdown is broken — it is that Markdown was designed for prose and inherited those defaults intact. A backslash is a reliable signal to any CommonMark-compatible renderer. A code span is a reliable signal to every renderer. Text that matters: use one of the two.


MNMNOTE stores your notes locally on your own device as plain Markdown files. Backslash escapes in your writing are yours to keep. mnmnote.com

Footnotes

  1. Stack Overflow API v2.3, questions 35827838; 5695341; 3666272; 5095977; 7756457; 5905879; 9044990 (site=stackoverflow), accessed 2026-11-06. Combined view counts as of access date; top question 35827838 ("how to show underscores symbol in markdown?") 177,278 views. ↩

  2. John Gruber, "Markdown Syntax Documentation," Daring Fireball, §Backslash Escapes. https://daringfireball.net/projects/markdown/syntax#backslash. Original escapable list: \ `` * _ { } [ ] ( ) # + - . ! ↩ ↩2

  3. Ahmed et al., "An Empirical Study of Markdown Usage in Software Engineering," arXiv preprint arXiv:2401.01472v3 (conference version: ICSME 2022; journal of record: Information and Software Technology 2024, doi 10.1016/j.infsof.2024.107478). Study analyzed 31,169,429 Stack Overflow answers. Statistics: 38.5% contain Code; 63.5% of Code-formatted uses highlight identifiers. https://arxiv.org/html/2401.01472v3 ↩

  4. Dave Binkley et al., "To CamelCase or Under_score," Proceedings of ICPC 2009, doi 10.1109/icpc.2009.5090039. N=135 developers; underscores and camelCase described as "two of the most popular conventions." Abstract accessed via OpenAlex, 2026-11-06. ↩

  5. CommonMark Specification 0.31.2, §2.4 Backslash escapes. https://spec.commonmark.org/0.31.2/#backslash-escapes. §6.2 Emphasis and strong emphasis, Example 356 (5*6*78 → 5<em>6</em>78). https://spec.commonmark.org/0.31.2/#emphasis-and-strong-emphasis ↩ ↩2 ↩3 ↩4 ↩5

  6. Own receipt, 2026-11-06: 34 escape cases × 10 setups — commonmark.js 0.31.2, markdown-it 14.1.0 (commonmark), markdown-it 14.1.0 (gfm), micromark 4.0.1 with gfm extension, Python-Markdown 3.11.3 (default), pandoc 3.13 (markdown, gfm, markdown_strict, commonmark), Markdown.pl 1.0.1. Merged output at receipts/all-out.json. ↩

  7. On GFM strikethrough (~~text~~), pandoc subscript (~x~), and tilde escaping across renderers: "Write H₂O in a Note and GitHub Crosses Out the 2: Highlight, Subscript, Superscript, and Underline Across Nine Renderer Setups", MNMNOTE Blog. ↩

  8. On ordered-list numbering and the year-becomes-a-list trap: "The Number You Type in a Markdown List Is Only a Suggestion", MNMNOTE Blog. ↩

  9. Markdown.pl 1.0.1 source (Gruber / Daring Fireball), L53: escape_table hash — 16 entries: \\ \ * _ { } [ ] ( ) > # + - . !`. https://daringfireball.net/projects/markdown/Markdown_1.0.1.zip ↩ ↩2

  10. Python-Markdown 3.11.2, markdown/core.py L112–114, ESCAPED_CHARS constant: 16 characters (Gruber's original set plus >). https://github.com/Python-Markdown/markdown/blob/3.11.2/markdown/core.py ↩

  11. Pandoc User's Guide, §Backslash escapes: "Inline TeX commands will be preserved and passed unchanged to the LaTeX and ConTeXt writers." https://pandoc.org/MANUAL.html#backslash-escapes ↩

  12. RFC 7764 §3.2 (IETF, Informational, 2016-03): lists "Multiple underscores in words" as the first stated difference between GFM and "Original Markdown." https://www.rfc-editor.org/rfc/rfc7764.txt ↩

  13. On placing a backtick inside a code span or fenced block, and the §2.4 "inside code" rule: "How to Put a Backtick Inside Code in Markdown — Tested in Six Engines", MNMNOTE Blog. ↩ ↩2

  14. On Markdown escaping behavior when pasting into Slack, Teams, and Telegram: "Paste a Markdown Note Into Slack, Teams, or Telegram and Your Italics Turn Bold", MNMNOTE Blog. ↩

  15. GitHub Docs, "Basic writing and formatting syntax," §Ignoring Markdown formatting, accessed 2026-11-06. Verbatim: "You can tell GitHub to ignore (or escape) Markdown formatting by using \ before the Markdown character." https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#ignoring-markdown-formatting ↩

  16. On the full list of escapable characters in CommonMark and the original Gruber set: The Complete Markdown Cheat Sheet, MNMNOTE Blog, §Escape characters. ↩