JsonMind
Encoding & Text9 min readUpdated Nov 10, 2026

Regex Cheat Sheet: Common Patterns and How to Test Them

A practical regular expression reference for anchors, classes, quantifiers, groups and lookarounds, with patterns, flags and safety checks that prevent surprises.

JMBy JsonMind EngineeringLast reviewed: Nov 10, 2026Reviewed against primary specifications and linked sources.
Quick answer

A regex is a sequence of tokens: character classes match one character, quantifiers repeat, anchors fix position, and groups combine. Use a tester with sample text, check greedy versus lazy matching, and be careful with lookarounds and nested quantifiers that can cause catastrophic backtracking.

Key takeaways
  • Escape metacharacters when you mean the literal character.
  • Greedy quantifiers match as much as possible; lazy quantifiers stop at the first chance.
  • Anchors match positions, not characters.
  • Named groups and lookarounds improve readability but are not supported identically everywhere.
  • Test with valid, invalid and adversarial input; nested quantifiers can hang a regex engine.

Regular expressions are a compact language for describing text patterns. They are also easy to write once and misread later. This cheat sheet covers the tokens you use most and the testing habits that prevent silent mistakes.

Literals and metacharacters

Most characters match themselves. These characters have special meaning and must be escaped to match literally:

. * + ? ^ $ { } [ ] ( ) | \

For example, \. matches a literal dot, and \* matches a literal asterisk. Escaping rules differ slightly between languages, especially inside character classes and replacement strings.

The escape problem usually appears one layer before the regex engine. In a JavaScript string, "\d" is just d because \d is not a recognized string escape; you need "\\d" or a regex literal /\d/. Python raw strings, r"\d", avoid the double backslash. Inside a character class, most metacharacters lose their meaning, but ], \, ^ and - still need care: [-a] puts the hyphen first, and [a\-z] is not the range you meant. Some engines also support \Q...\E to quote a literal run without escaping each character, but JavaScript and Python do not.

Character classes

PatternMatches
.Any character except a line break (unless dotAll)
\dA digit, usually [0-9]
\wA word character: letters, digits and underscore
\sWhitespace
[abc]One of a, b or c
[^abc]Any character except a, b or c
[a-z]A character in the range a to z
[A-Za-z0-9_]The explicit form of \w in ASCII

Negated classes are often clearer than lazy quantifiers. "[^"]*" matches a quoted string up to the closing quote.

The shorthand classes are not as universal as they look. In JavaScript without the u flag, \d means [0-9], \w means [A-Za-z0-9_] and \s covers a fixed ASCII set. With u, \d and \w remain ASCII-only, while \s expands to Unicode whitespace. In Python 3, \d, \w and \s are Unicode-aware by default for str patterns, so \w+ matches Cyrillic and CJK text, and \d matches digits such as Arabic-Indic numerals. If you need ASCII-only behavior in Python, use re.ASCII or the explicit class. This is a common source of patterns that behave differently in a browser and on the server.

A dot is also narrower than many people assume: by default it excludes \n, and in JavaScript without s it excludes \r plus the Unicode line and paragraph separators. Use [^] in JavaScript when you truly mean any UTF-16 code unit, or [\s\S] when you want a portable any-character class.

Quantifiers

PatternMeaning
*Zero or more
+One or more
?Zero or one
{3}Exactly three
{2,5}Two to five
{2,}Two or more
*?Zero or more, lazy
+?One or more, lazy

Greedy quantifiers match as much as possible. Lazy quantifiers match as little as possible. ".*" matches from the first quote to the last; ".*?" matches one quoted segment.

Lazy is not the same as efficient. .*? still tries to match, fails, advances one character and tries again, so a lazy quantifier over a long non-matching string can be quadratic. Possessive quantifiers, written *+, ++ or ?+ in PCRE, Java and Perl, consume without backtracking; atomic groups (?>...) do the same for a subexpression. They are the right tool when you know the quantifier should never give characters back. JavaScript does not have them, but it does support lookahead, which can emulate atomic grouping in some cases.

Anchors and boundaries

PatternMeaning
^Start of the string, or line with the m flag
$End of the string, or line with the m flag
\bWord boundary
\BNot a word boundary
\AStart of the string in some engines
\zEnd of the string in some engines

Anchors match positions, not characters. ^$ matches an empty line, not a line containing spaces.

The end anchors are not interchangeable across engines. In Python 3.14 and later, \A and \z always mean start and end of the whole string, \Z also allows a final newline, and $ matches before a trailing newline even without multiline mode. In JavaScript, $ without m matches only at the very end, so "abc\n" fails c$ unless you strip the newline. \b is a position between a word character and a non-word character; it is not a character, so a pattern like \b alone matches zero characters and highlights a boundary. With Unicode input, whether \b treats a CJK character as a word character depends on the engine's u flag and definition of word character.

Groups and alternation

  • Capturing group: (abc) captures the matched text.
  • Non-capturing group: (?:abc) groups without capturing.
  • Named group: (?<name>abc) captures under a name.
  • Backreference: \1 or \k<name> refers to an earlier capture.
  • Alternation: cat|dog matches either.

Alternation has low precedence. ^cat|dog$ means ^cat or dog$. Use ^(cat|dog)$ when the anchors should apply to both.

Alternation is ordered, not symmetric. cat|category matches the cat inside category; the engine never tries the longer branch once the first succeeds. When one alternative is a prefix of another, put the longer one first or anchor the match. Capturing group numbers are assigned by the order of opening parentheses, not by the order in which they match, so inserting a group in the middle renumbers every later backreference. Named groups avoid that fragility, but the syntax differs: JavaScript and PCRE use (?<name>...), Python uses (?P<name>...), and backreferences are \k<name> in JavaScript and (?P=name) in Python. Replacement syntax differs again, which is why a regex that works in the tester can fail in code.

Lookarounds

PatternMeaning
(?=abc)Followed by abc
(?!abc)Not followed by abc
(?<=abc)Preceded by abc
(?<!abc)Not preceded by abc

Lookarounds assert context without consuming it. \d+(?= dollars) matches the number in "25 dollars" but not in "25 euros". Support and maximum lengths vary by engine; JavaScript and Python support lookbehind, but some older engines do not.

Lookbehind is the most fragile feature in portable patterns. Python requires a fixed-width lookbehind: (?<=abc) works, (?<=a|bc) does not, and (?<=\d+) is rejected because the width is unbounded. JavaScript's lookbehind is more permissive and allows variable length, as do .NET and PCRE2 in many cases. A regex tested in Chrome can therefore fail to compile in Python. If the pattern must run in both, replace the lookbehind with a capturing group and adjust the replacement, or filter candidates in code.

Flags

FlagEffect
iCase-insensitive
mMultiline: ^ and $ match line boundaries
sDot matches line breaks
gGlobal: find all matches
uUnicode mode
ySticky: match at the current position

Flags change semantics, not just convenience. A pattern tested without m can behave differently when m is enabled.

Two flags cause state bugs. In JavaScript, a regex with g keeps a lastIndex property, so calling .test() twice on the same regex object can return true and then false for the same string; the second call starts where the first stopped. Either reset lastIndex or create the regex inside the loop. With u, the engine treats the pattern as Unicode code points, so . matches an emoji as one code point rather than two surrogate halves, and \p{Letter} becomes available. The newer v flag extends u with set notation such as [\p{Letter}--[a-z]]. Neither u nor v changes \d to match non-ASCII digits in JavaScript; that still requires an explicit Unicode property class.

Common patterns

  • Digits only: ^\d+$
  • Integer with optional sign: ^[+-]?\d+$
  • Hex color: ^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$
  • ISO date (shape only): ^\d{4}-\d{2}-\d{2}$
  • Slug: ^[a-z0-9]+(?:-[a-z0-9]+)*$
  • Simple email shape: ^[^\s@]+@[^\s@]+\.[^\s@]+$
  • URL with scheme: ^https?://[^\s/$.?#].[^\s]*$

These patterns validate shape, not semantics. 2026-99-99 passes the date shape. Pair the regex with a real date parser when validity matters. Likewise, a simple email pattern cannot confirm that an address exists.

The URL pattern above is deliberately shallow: it accepts http:// with almost anything after it and rejects valid inputs with ports, IPv6 literals, userinfo or internationalized domains. If you need to validate a URL, parse it with the platform's URL constructor and check the protocol and host; a regex cannot correctly handle the grammar. The email pattern is also easy to turn into a denial-of-service risk if it is extended with nested quantifiers; keep it short and verify by sending mail. For dates, check the shape with a regex and then construct a Date or use a library, because 2026-02-30 and 2026-13-01 both pass the shape. For colors, #abc and #aabbcc are valid, but #abcd is not; the pattern above already encodes that.

Greedy, lazy and catastrophic backtracking

Nested quantifiers can explode. The pattern (a+)+$ against a long string of a followed by b may try exponentially many ways to split the input.

Practical defenses:

  • Avoid nested quantifiers over the same characters.
  • Prefer explicit character classes over . plus repetition.
  • Use possessive quantifiers or atomic groups where supported.
  • Set a timeout where the engine allows it.
  • Test adversarial input, not only matching input.

Here is what exponential means in practice. Against (a+)+$, the string aaaaaaaaaaaaaaaaaaaaaaaaaaaaaab with 30 as can be split into groups in 2^29 ways, roughly 536 million paths. A backtracking engine such as JavaScript's or PCRE may spend seconds to minutes before reporting failure; at 40 as the same input can outlast any request timeout. ^(a|aa)+$ and ^(\w+\s?)*$ show the same behavior on similarly short inputs. The curve is steep enough that a test with 10 characters looks fine and a production string with 50 characters hangs.

The engine matters. RE2, Go's regexp, and Rust's regex crate run in linear time because they do not backtrack, but they also omit backreferences and some lookaround features. If your pattern needs those features, keep it on a backtracking engine and enforce an input length limit and a timeout. In Node.js, a catastrophic regex blocks the entire event loop, not just one request, so the failure takes down unrelated traffic.

Replacement strings

In JavaScript, $1 and $<name> insert captures. In Python, \1 and \g<name> are used. In some tools, $& means the whole match. Replacement syntax varies by language and tool, so check the target dialect.

A few more replacement tokens are worth knowing. JavaScript also supports $$ for a literal dollar sign, $& for the whole match, $` for the text before the match and $' for the text after it. Python's \g<0> means the whole match, and \1 is ambiguous when followed by a digit group, so \g<1>0 is safer. In both languages, an unmatched optional group inserts an empty string rather than preserving the original text, which can silently delete content. If the replacement needs logic, use a function: JavaScript accepts a replacer function, Python accepts a function as the second argument to re.sub, and both give you the match object instead of a template string.

The regex tester highlights matches and capture groups against sample text. The regex generator builds a pattern from plain-language requirements, and the find and replace tool applies patterns to real text. Verify the generated pattern with test cases before using it in code.

Testing checklist

  1. Test at least one valid input.
  2. Test one invalid input that should not match.
  3. Test empty input.
  4. Test input with line breaks and Unicode characters.
  5. Check anchored versus unanchored behavior.
  6. Check the dialect and flags used in production.
  7. Test performance on a long, non-matching string.
  8. Store tricky patterns with comments and examples.
  9. Run the same test cases against the engine used in production, not only the tester.
  10. Add a length limit before the regex runs, even if the pattern looks safe.

A regex is code. Keep it readable, test it with real examples, and treat any pattern with nested quantifiers as something to review.

Some jobs should not use a regex at all. Parsing HTML with a regex breaks on nested tags, comments and attributes containing >; use an HTML parser. Validating JSON, CSV or URLs with a regex reimplements a grammar that already has a parser, and usually gets edge cases wrong. Checking whether a string contains a fixed substring, starts with a prefix or splits on a delimiter is faster and clearer with includes, startsWith or split. Reach for a regex when the pattern is genuinely irregular but still regular, and reach for a parser when the input has nesting or a formal grammar.

Frequently asked questions

What does the dot match?

By default, the dot matches any character except a line break. With the s or dotAll flag, it also matches line breaks. Use a negated character class such as [^\n] when you want explicit control.

What is the difference between * and +?

The star quantifier matches zero or more repetitions, so the pattern can match an empty string. The plus quantifier matches one or more, so at least one occurrence is required.

Why does my regex match too much text?

A greedy quantifier consumes as much as possible, then backtracks. If you want the shortest match, use a lazy quantifier such as .*? or make the surrounding pattern more specific.

Is regex a good way to validate email addresses?

A simple regex can reject obvious mistakes, but full email validation is complicated and changes over time. Validate the basic shape with a regex, then confirm the address by sending a verification message.

What is catastrophic backtracking?

Certain patterns, especially nested quantifiers such as (a+)+, can require exponential time on non-matching input. Limit nesting, keep alternatives distinct, and test with adversarial strings.

References

Put this guide into practice

Browse all tools

Open the browser tool. Inputs stay in your browser and are not uploaded to the business server.

Related guides

All guides