Javid
·18 min read

Regex Cheat Sheet: Syntax, Flags, and What Breaks Between Engines

SelfDevKit Regex Validator showing a regex cheat sheet, pattern flags, and live match highlighting

Most regex cheat sheets hand you a table of symbols and wish you luck. That works right up until the pattern you copied fails in Go, silently changes meaning in grep, or gets mangled by your language's string escaping. This regex cheat sheet includes the syntax tables you expect, plus the portability notes that decide whether the pattern actually works where you paste it.

The short version

A regex cheat sheet is a compact reference for regular expression syntax: character classes (\d, \w, \s), quantifiers (*, +, ?, {n,m}), anchors (^, $, \b), groups ((...)), and flags (i, m, s). The syntax is roughly 90% identical across engines. The remaining 10%, mostly lookbehind, backreferences, named groups, and Unicode behavior, is where patterns break when moved between languages.

Table of contents

Regex cheat sheet: characters and character classes

Character classes define which single characters a position can match. These are the tokens you use in almost every pattern.

Token Matches Portability note
. Any character except newline Universal. Add the s / DOTALL flag to include newlines
\d \D Digit / non-digit Unicode-aware in Python, Rust, and .NET. ASCII-only in JavaScript, Go, and Java
\w \W Word char / non-word char Same split as \d. JavaScript \w is always [A-Za-z0-9_]
\s \S Whitespace / non-whitespace Universal in practice
[abc] Any one of a, b, c Universal
[^abc] Any character except a, b, c Universal
[a-z0-9] Range union inside a class Put - first or last to match a literal hyphen
[\d\-.] Classes nest inside classes Escape - or place it at the edge
\. \/ \\ Literal metacharacter Inside [...] most metacharacters lose their meaning and need no escape
\p{L} \p{Nd} Unicode property (letter, decimal number) Requires the u flag in JavaScript. Not available in POSIX grep
[[:alpha:]] [[:digit:]] POSIX class The only portable named class in BRE/ERE grep. Rare in application code
\t \n \r \f \v Tab, newline, carriage return, form feed, vertical tab Universal
\xhh \uFFFF Character by hex code point Syntax varies: Rust and Python also accept \x{1F600} style

If you write mostly JavaScript, MDN's regular expressions cheatsheet is the authoritative list of what the ECMAScript engine accepts. The tables here stay engine-neutral on purpose.

Two class rules cause more bugs than the rest combined. Inside a bracket class, . is a literal dot, so [.] needs no backslash. And outside a class, - is always literal, so \- is harmless but unnecessary noise.

Quantifiers

Quantifiers control repetition. Greedy by default, lazy when you add ?, possessive when you add + on the engines that support it.

Token Meaning Greedy Lazy Possessive
* 0 or more * *? *+
+ 1 or more + +? ++
? 0 or 1 ? ?? ?+
{n} Exactly n {n} {n}? {n}+
{n,} n or more {n,} {n,}? {n,}+
{n,m} Between n and m {n,m} {n,m}? {n,m}+

Possessive quantifiers exist in Java, PCRE2, and Ruby (Oniguruma). They are documented in the Java Pattern javadoc as X?+, X*+, X++. They do not exist in JavaScript, Python's standard re, Go, or Rust.

The greedy versus lazy difference is the classic HTML-scraping trap:

Input:  <b>bold</b> and <i>italic</i>
<.+>    matches the entire line
<.+?>   matches <b>, then </b>, then <i>, then </i>
[^>]+   matches the same as the lazy version, faster

[^>]+ is usually the right answer. A negated character class does the same job as a lazy quantifier without the backtracking cost. If you want to understand why that cost matters, our guide on how to test regex covers catastrophic backtracking and how to measure it.

Anchors and boundaries

Anchors match a position, not a character. They consume nothing, which is why ^$ matches an empty string and why \b\b is legal but pointless.

Token Matches Notes
^ Start of string With the m flag, start of every line
$ End of string With m, end of every line. See the trailing-newline warning below
\A Absolute start of string Not in JavaScript. Ignores the m flag
\z Absolute end of string Not in JavaScript
\Z End of string, before a final newline Java and Python. Do not confuse with \z
\b Word boundary Defined by \w on each side, so its Unicode behavior follows \w
\B Not a word boundary Useful for "inside a word" searches
\G End of the previous match PCRE, Java, .NET. Not in JavaScript, Python, Go, or Rust

The $ behavior is worth memorizing because it silently changes what your validator accepts. Running the same pattern against "abc\n":

# Python 3
re.search(r'c$',  'abc\n')   # matches: $ allows a trailing newline
re.search(r'c\Z', 'abc\n')   # None: \Z is the true end of input
// Node.js
/c$/.test('abc\n')   // false: JavaScript $ means the actual end

Both results were verified locally on Python 3 and Node 24. If you validate user input with ^...$ in Python, a trailing \n sneaks past, which the re module documentation spells out. Use \Z in Python when you mean the end of the input, and note that Java reverses the meaning: Java's \Z is "the end of the input but for the final terminator" while \z is the hard end.

Groups, alternation, and lookarounds

Token Meaning Portability note
(...) Capture group, numbered left to right Universal
(?:...) Non-capturing group Universal
(?<name>...) Named capture group JavaScript, .NET, Java, Rust, PCRE2
(?P<name>...) Named capture group, Python style Python and Rust. A syntax error in JavaScript
a|b Alternation, leftmost match wins Universal. Order matters: put the longer alternative first
\1 \2 Backreference to a captured group Not supported in Go or Rust
\k<name> Named backreference Not supported in Go or Rust
(?=...) Positive lookahead Not supported in Go or Rust
(?!...) Negative lookahead Not supported in Go or Rust
(?<=...) Positive lookbehind Fixed width only in Python. Variable width in JavaScript and .NET
(?<!...) Negative lookbehind Same constraints as lookbehind
(?#...) Inline comment PCRE, Python, .NET. Not JavaScript

Alternation ordering is a real bug source. (cat|category) matching against category captures cat, because most engines take the first alternative that allows the overall match to succeed rather than the longest one. Write (category|cat).

Lookbehind width limits are engine-specific and verified easily:

re.compile(r'(?<=ab{1,3})c')
# error: look-behind requires fixed-width pattern
/(?<=ab{1,3})c/.test('abbc')   // true

Flags and inline modifiers

Flags change how the whole pattern is interpreted. Every flag below also has an inline form you can embed in the pattern itself, which is the portable way to carry a flag along with the pattern string.

Flag Inline Effect
i (?i) Case-insensitive matching
m (?m) Multiline: ^ and $ match at line boundaries
s (?s) Dot matches newline (DOTALL / single-line)
x (?x) Extended: ignore whitespace and allow # comments in the pattern
u n/a Unicode mode. Required in JavaScript for \p{...}
U (?U) Swap greedy and lazy quantifiers (PCRE, Rust, Go)
g n/a Global: return every match, not just the first. A JavaScript and tool-level concept, not a pattern feature

Inline modifiers can be scoped to part of the pattern with (?i:...), and switched off with a leading minus: (?-i:...). That scoping is the cleanest way to make one section case-insensitive without touching the rest.

The extended flag is underused. It turns an unreadable pattern into something a reviewer can approve:

(?x)
  ^(\d{4})      # year
  -(\d{2})      # month
  -(\d{2})$     # day

The same pattern, different answers per engine

The most important thing a regex cheat sheet can tell you is where the same pattern produces a different result. These are the five differences that actually bite, with sources.

Behavior JavaScript Python 3 Go (RE2) Rust regex Java .NET
\d matches non-ASCII digits No Yes No Yes No Yes
Lookahead (?=...) Yes Yes No No Yes Yes
Lookbehind width Variable Fixed only Not supported Not supported Bounded Variable
Backreference \1 Yes Yes No No Yes Yes
Named group syntax (?<n>) (?P<n>) both (Go 1.22+) both (?<n>) (?<n>)

The Unicode digit row surprises people, so here it is running against the Arabic-Indic digits ٣٤:

re.fullmatch(r'\d+', '٣٤')                  # matches
re.fullmatch(r'\d+', '٣٤', re.ASCII)        # None
/^\d+$/.test('٣٤')   // false

That means a "digits only" validator ported from JavaScript to Python quietly starts accepting numerals your downstream int() parse may still handle, but your database column or downstream service may not. The Rust regex crate documents the same Unicode-aware default: \d is \p{Nd}, and you opt out per-token with (?-u:\d).

The missing lookahead in Go and Rust is not an oversight. Both use finite-automata engines derived from RE2. The Go regexp package states that it "is guaranteed to run in time linear in the size of the input," and the Rust regex crate docs say the syntax "lacks several features that are not known how to implement efficiently. This includes, but is not limited to, look-around and backreferences." You trade two features for immunity to catastrophic backtracking. For anything that runs against untrusted input, that is a good trade, and OWASP's ReDoS page explains what happens when you do not have it.

Regex on the command line: grep, sed, and ripgrep

Command-line tools use a different, older regex dialect than your application code, and this is where copied cheat-sheet patterns fail most often. GNU grep supports three dialects: basic (BRE, the default), extended (-E), and Perl-compatible (-P).

Every result below was run against GNU grep 3.11 on the input abc123:

Pattern grep (BRE) grep -E (ERE) grep -P (PCRE)
\d\+ / \d+ No match No match Matches
[[:digit:]]\+ Matches Matches Matches
\w\+ / \w+ Matches (GNU extension) Matches Matches
foo(?=1) No match (warns) No match (warns) Matches
+ ? { ( ) | Literal characters, escape to activate Metacharacters Metacharacters

Three rules follow from that table:

  1. \d does not exist in POSIX regex. Use [0-9] or [[:digit:]] unless you pass -P. This single fact explains most "but it worked in the tester" bug reports.
  2. In BRE, the operators are backslashed. a\+, \(group\), a\{2,3\}, cat\|dog. In ERE, they are bare. Reach for grep -E and sed -E by default so the syntax matches what everyone else writes.
  3. grep -P is not always available. It is an optional feature in GNU grep and absent from BSD/macOS grep. ripgrep sidesteps this by using the Rust engine everywhere, with the same no-lookaround tradeoff described above.

The GNU grep manual documents the full BRE and ERE dialects if you need the edge cases.

Shell quoting adds a second layer. Single quotes are the safe default because the shell will not touch $, \, or ! inside them:

grep -E '^[0-9]{3}-[0-9]{4}$' file.txt     # correct
grep -E "^[0-9]{3}-[0-9]{4}$" file.txt     # $ inside double quotes invites expansion

Escaping your pattern for the host language

A regex is a string before it is a pattern, and every language escapes strings differently. This table shows the exact source you need to express the pattern \d{4}-\d{2} in each host.

Host How you write it Why
JavaScript literal /\d{4}-\d{2}/ Literal syntax, no string escaping
JavaScript string new RegExp("\\d{4}-\\d{2}") \d is not a valid string escape, so double it
Python r'\d{4}-\d{2}' Raw strings exist for exactly this
Java "\\d{4}-\\d{2}" The javadoc requires doubling backslashes in string literals
Go `\d{4}-\d{2}` Backtick raw string literals
C# @"\d{4}-\d{2}" Verbatim string literal
JSON config "\\d{4}-\\d{2}" JSON has no raw strings. Every backslash doubles
YAML (single-quoted) '\d{4}-\d{2}' Single-quoted YAML scalars do not process backslash escapes
Shell '\d{4}-\d{2}' Single quotes, always

The JSON row causes the worst debugging sessions, because the doubling happens at a layer you are not looking at. A pattern in an ESLint config, a Prometheus relabel rule, or a Logstash filter is a JSON or YAML string first. If you are staring at a config value and cannot tell how many backslashes survived, run it through a JSON viewer and unescaper to see the decoded string, or read our walkthrough on unescaping JSON strings.

One more escaping rule: when you build a pattern from user input, escape it programmatically. re.escape() in Python, regexp.QuoteMeta() in Go, and RegExp.escape() in modern JavaScript all exist so that a search term containing . or ( does not become a syntax error or an accidental wildcard.

Search and replace syntax

Replacement strings have their own syntax, and it does not match the pattern syntax. This is the table almost no regex cheat sheet includes.

Environment Group by number Group by name Whole match
JavaScript $1 $<name> $&
Python \1 \g<name> \g<0>
Java / .NET $1 ${name} $0
sed / Perl \1 (sed), $1 (Perl) n/a in sed & (sed), $& (Perl)
Go $1 or ${1} ${name} $0

All of these were verified by running them:

echo 'user@host' | sed -E 's/(\w+)@(\w+)/\2:\1/'     # host:user
echo 'user@host' | perl -pe 's/(\w+)\@(\w+)/$2:$1/'  # host:user
re.sub(r'(\w+)@(\w+)', r'\2:\1', 'user@host')        # host:user
re.sub(r'(\w+)@(\w+)', r'$2:$1', 'user@host')        # '$2:$1' literally

Use ${1} or \g<1> whenever a digit follows the reference. $12 is group 12, not group 1 followed by 2.

Reading a regex you did not write

Cheat sheets are usually organized the wrong way for the most common task: you have a pattern in front of you from a config file or an old PR and you need to know what it does. Read it left to right, splitting at the top-level alternation and group boundaries first.

^(?:[a-z0-9._%+-]+)@(?:[a-z0-9-]+\.)+[a-z]{2,}$
Fragment Meaning
^ Anchor at the start of the string
(?: Non-capturing group, so no capture number is assigned
[a-z0-9._%+-]+ One or more of these characters. The - is last, so it is literal
)@ Close group, then a literal at sign
(?:[a-z0-9-]+\.)+ One or more repetitions of "label followed by a dot"
[a-z]{2,} A TLD of two or more letters
$ Anchor at the end

Three habits make this faster. Count the unescaped ( to know how many capture groups exist. Check whether every . is escaped, because an unescaped one is a wildcard. And scan for nested quantifiers like (a+)+, which is the shape of a catastrophic backtracking bug.

Also worth saying plainly: some things should not be regex at all. Parsing a URL into scheme, host, path, and query with a pattern is a losing game against percent-encoding and internationalized domains. Use a real URL parser instead. The same applies to HTML and to JSON, where a structural validator beats any pattern you can write.

Keeping the cheat sheet next to the tester

A cheat sheet is most useful in the two seconds between "I need a word boundary" and "does this engine call it \b?" That is why SelfDevKit ships the reference inside the tool: the Regex Validator has a built-in cheat sheet panel, flag toggles with plain-English labels, and live match highlighting with capture groups and match indices.

SelfDevKit Regex Validator with live match highlighting, flag toggles, and a built-in regex cheat sheet

Two details matter for how you use it. First, the matching engine is the Rust regex crate, so patterns behave exactly like Go, ripgrep, and RE2: linear-time matching, no lookaround, no backreferences. If your pattern is destined for a Go service or a ripgrep invocation, testing it here catches the incompatibility immediately rather than in CI. If it is destined for JavaScript, use the flavor table above and verify the lookaround parts in your runtime.

Second, nothing leaves the machine. Regex work usually means pasting a sample of the data you are matching against, and that sample is often production log lines, customer emails, or an access token you are trying to extract. Online testers receive that payload on a server you do not control. A desktop app does not, which is the whole argument in why offline matters for developer tooling. For heavier text work on the same data, the text inspector gives you character counts, encoding checks, and line stats without a network round trip either.

Frequently asked questions

Why does my regex work in an online tester but not in my code?

Usually one of two reasons. The tester used a different engine, so a lookahead or backreference that worked there is unsupported in Go or Rust. Or the pattern survived the tester fine but got mangled by string escaping in your source file or config. Check the escaping table above first, then the engine table.

Which regex flavor should I learn from a cheat sheet?

Learn the PCRE-style syntax that JavaScript, Python, Java, and .NET share, since that covers most application code. Then memorize two exceptions: POSIX tools like grep and sed have no \d, and RE2-based engines like Go and Rust have no lookaround or backreferences.

Is \d the same as [0-9]?

Not always. In Python, .NET, and Rust, \d matches any Unicode decimal digit, including Arabic-Indic and Devanagari numerals. In JavaScript, Go, and Java it is ASCII [0-9] by default. If you mean exactly the ASCII digits, write [0-9] and remove the ambiguity.

How do I make a regex faster?

Replace lazy quantifiers with negated character classes ([^>]+ instead of .+?), anchor the pattern so it fails early, and avoid nesting quantifiers inside groups. If you need a hard guarantee against pathological input, use an engine with linear-time matching. The regex tester guide has the full pattern library if you want worked examples.

Try the cheat sheet where you actually work

Copying a pattern from a web page into a tester into your editor is three chances to lose a backslash. Keeping the reference, the tester, and the flag toggles in one offline window removes all three.

Download SelfDevKit to get the Regex Validator plus 50+ other developer tools, offline and private.

Related Articles

How to Test Regex: A Practical Guide to Testing Patterns That Work
DEVELOPER TOOLS

How to Test Regex: A Practical Guide to Testing Patterns That Work

Learn how to test regex the right way: write test cases, handle engine flavor differences, catch catastrophic backtracking, and debug patterns offline.

Read →
Regex Tester Guide: Learn Regular Expressions with Practical Examples
DEVELOPER TOOLS

Regex Tester Guide: Learn Regular Expressions with Practical Examples

Master regex with this comprehensive regex tester guide. Learn regex syntax, common patterns for validation, capture groups, and how to test expressions effectively with real-world examples.

Read →
Why Offline-First Developer Tools Matter More Than Ever
DEVELOPER TOOLS

Why Offline-First Developer Tools Matter More Than Ever

Discover why privacy-focused, offline developer tools are essential in 2025. Learn how local processing protects your API keys, JWT tokens, and sensitive data while delivering instant performance.

Read →