Regular expressions are a tiny language for describing text patterns, and like any language you use it in bursts and then forget half of it. The syntax below is grouped the way you tend to need it: the classes that match kinds of character, the anchors that pin a match to a position, the quantifiers that control repetition, then groups, lookarounds and flags. Everything is written for the common engines you meet day to day, and where they disagree, the disagreement is called out rather than glossed over.
The examples use JavaScript regex syntax unless noted, because it is the flavor in your browser, your regex tester, and most of the config files you touch. When Python or PCRE (the engine behind PHP, and close to what grep -P and many others use) differs in a way that matters, there is a note.
Character classes
A character class matches one character out of a set. Square brackets build your own set, and the backslash shorthands cover the common ones.
Inside a class most metacharacters go quiet, so [.] is just a dot and [+*?] is those three literal symbols. The hyphen is the exception you have to think about: it builds a range in the middle, so to match a literal hyphen put it first or last, as in [-a-z] or [a-z-].
Anchors and boundaries
Anchors match a position rather than a character. They consume nothing; they assert that you are at a certain spot.
The word boundary \\b is the one people underuse. It is why \\bcat\\b matches the word cat but not the cat inside category or scatter. Note that ^ and $ change meaning under the multiline flag: without it they anchor to the whole string, with it they anchor to each line.
Quantifiers: greedy versus lazy
Quantifiers say how many times the thing before them may repeat. Each one has a greedy default and a lazy variant formed by adding a question mark.
Greedy is the default and it matches as much as possible before backing off. Lazy matches as little as possible and only grows when the rest of the pattern demands it. The classic trap is matching between delimiters: on the string below, greedy runs to the last bracket while lazy stops at the first.
When a lazy quantifier still feels fiddly, matching a negated class is often clearer and faster: <[^>]*>reads as “a less-than, then anything that is not a greater-than, then a greater-than,” which does the same job without backtracking.
Groups and backreferences
Parentheses group part of a pattern so a quantifier can apply to the whole group, and they capture what they match so you can reuse it, in a replacement or later in the pattern.
A backreference matches the same text a group captured earlier. Inside the pattern you write \\1 for the first group; in a replacement string most tools use $1. So (\w)\1 finds a doubled letter, matching the ll in hello.
Lookarounds
A lookaround checks whether something does or does not appear next to your match, without including it in the match. Look ahead of the current position, or behind it, and assert either presence or absence.
Lookaheads let you require a condition without consuming characters. A common use is a password-style check: ^(?=.*\d)(?=.*[A-Z]).{8,}$asserts “somewhere there is a digit” and “somewhere there is an uppercase letter” before matching eight or more of anything. Lookbehind is handy for grabbing a value after a label, like (?<=\$)\d+ to pull the number after a dollar sign without capturing the sign.
The portability caveat lives here. Lookahead is universal. Lookbehind is newer: it landed in JavaScript with ES2018 and works in current Node and browsers, and PCRE and Python support it too, but some older or lighter engines do not. Python additionally requires its lookbehind to be fixed-length, so (?<=ab) is fine while(?<=a+) is a syntax error there; JavaScript and PCRE are more relaxed about that.
Flags
Flags change how the whole pattern is applied. In JavaScript they hang off the end of a literal, as in /pattern/gi; in Python they are passed to the function or written inline as (?i) at the start.
Two of these sound related but do separate jobs. The multiline flag (m) only changes what ^ and $ mean; it does nothing to the dot. The dotall flag (s) only changes the dot; it does nothing to the anchors. To make the dot cross newlines, reach for s; m is the one that moves the anchors. The extended flag (x), available in PCRE and Python but not plain JavaScript, lets you spread a gnarly pattern over several lines with comments, which is worth its weight on anything you will reread.
Patterns you actually reuse
Cheat sheets earn their keep on the patterns you would otherwise rewrite. These are safe, self-contained ones. Test any of them live in the regex tester, which runs the browser’s own JavaScript engine, so what matches there matches in your code.
Trim leading and trailing whitespace
Match the whitespace at the very start or the very end of a line and replace it with nothing. Add the multiline flag so it applies at every line’s edges rather than only the ends of the whole string.
Extract every run of digits
Pull each number out of mixed text. With the global flag this yields one match per number; each match is a full run of digits rather than single characters.
Match a whole line
Under the multiline flag, this captures the content of each line without the line breaks. The * allows empty lines to match too.
Match an ISO date
A shape check for a YYYY-MM-DD date, anchored so it matches the whole field. This validates the format, not the calendar: it will accept 2026-13-40, so treat it as a first pass and parse the result to confirm the day is real.
A word on that last caveat, because it applies broadly: a regex is good at shape and bad at meaning. It can tell an email-shaped string from a random one, but it cannot tell you the address exists, and the fully correct email regex is famously enormous. For validation, match a sensible shape and let a real parser or a live check do the rest.
Where flavors diverge, briefly
- Lookbehind: everywhere modern except some lightweight engines; Python needs it fixed-length.
- Named groups: (?<name>...) in JS, PCRE and .NET; (?P<name>...) in Python.
- \\A and \\z: real string anchors in PCRE and Python; JavaScript has only ^ and $.
- Unicode \\w and \\d: ASCII by default in JS and PCRE, Unicode by default in Python 3.
- The x flag: PCRE and Python let you comment and space out patterns; plain JavaScript does not.
For the formats you paste around a shell all day, a clean copy matters as much as a correct pattern; our guide to copy and paste in the terminal covers the shortcuts that will not mangle it. And if you keep coming back to jq or subnet math, the sibling jq cheat sheet and subnet cheat sheet follow the same format.
Frequently asked questions
Do I need to escape a dot inside a character class?
No. Inside square brackets, a dot is a literal period, so [.] and \. both match a single dot. Most metacharacters lose their special meaning inside a class. The characters that still need care in there are ^ (only special as the first character, where it negates), the closing ], the backslash, and the hyphen, which forms a range unless it sits first or last.
What is the difference between greedy and lazy quantifiers?
A greedy quantifier like .* grabs as much text as it can and then gives back only what it must to let the rest of the pattern match. A lazy quantifier like .*? grabs as little as possible and expands only when forced. On the string <a><b>, the pattern <.*> matches the whole thing, while <.*?> matches just <a>. Reach for lazy when you are matching between delimiters.
Does JavaScript support lookbehind?
Modern JavaScript does. Lookbehind, both positive (?<=...) and negative (?<!...), has been part of the language since ES2018 and works in current Node and every current browser. Older runtimes and some other engines lack it, which is the main portability gotcha. Lookahead, by contrast, has worked everywhere for a long time.
Why does my pattern match too much across newlines?
By default the dot matches any character except a line break, and the ^ and $ anchors bind to the whole string. If you want the dot to cross newlines, add the dotall flag (s). If you want ^ and $ to match at the start and end of every line, add the multiline flag (m). They are separate switches and are easy to confuse.