How to Use Regex: A Practical Guide With a Cheat Sheet
A regular expression is a search pattern. Instead of searching for the word cat, you search for "three digits, a dash, three digits, a dash, four digits" — and every phone number in a 10,000-line file lights up at once. That is the whole idea. Everything else is notation.
The notation is what scares people off. ^(?=.*[a-z])(?=.*\d).{8,}$ looks like something went wrong with your keyboard. But regex has roughly eight symbols that do ninety percent of real work, and you can learn them in about twenty minutes if you have somewhere to try them as you go.
That is the important part: nobody writes regex correctly on the first attempt. Professional developers build patterns the same way everyone else does — type a bit, look at what matched, adjust, repeat. Open the free Regex Tester in a second tab, paste some real text into it, and build every pattern in this guide as you read. It runs entirely in your browser, so you can paste production logs into it without sending them anywhere.
The Eight Symbols That Do Most of the Work
Learn these before anything else. Almost every pattern you will ever write is a combination of them.
| Symbol | Means | Example | Matches |
|---|---|---|---|
| \d | Any digit 0–9 | \d\d\d | 604 |
| \w | Letter, digit or underscore | \w+ | order_id |
| \s | Any whitespace | \s+ | spaces, tabs, newlines |
| . | Any character at all | a.c | abc, a7c, a c |
| + | One or more of the last thing | \d+ | 7, 42, 10429 |
| * | Zero or more of the last thing | -* | nothing, -, --- |
| ? | The last thing is optional | colou?r | color, colour |
| [ ] | Any one character from this set | [ABC] | A, B or C |
Two more that pay for themselves immediately:
{3}— exactly three of the last thing.\d{4}is four digits.{2,5}— between two and five.\d{8,}is eight or more.
Uppercase inverts the shorthand: \D is "not a digit", \W is "not a word character", \S is "not whitespace". [^abc] is "any character except a, b or c" — the caret inside brackets means "not".
How to Test a Regex Online
Build patterns against real text, not from memory. Here is the loop:
- Open the Regex Tester.
- Paste a realistic sample into the Test String box — a few lines of your actual log, CSV or document. Include the awkward rows, not just the tidy ones.
- Type your pattern into the Regular Expression field. Do not include the surrounding slashes — the tool shows
/and/gas decoration around the box. Type\d{3}, not/\d{3}/. - Set the Flags field. It defaults to
g, which is what you want almost always — see the next section. - Watch the matches highlight in yellow as you type. There is no "run" button; it re-evaluates on every keystroke.
- If your pattern is malformed, the field turns red and prints the exact engine error, for example
Invalid regular expression: /[a-/g: Unterminated character class. That message is usually enough to find the problem.
Everything runs in your browser — the pattern and the test text never leave your device. That matters more than it sounds: log lines, customer exports and API responses are exactly the kind of text people paste into regex testers, and most online testers post it to a server.
Build the pattern in small steps. Match \d first, confirm digits light up, then extend to \d{3}, then \d{3}-\d{4}. When something stops matching you will know precisely which character broke it.
Flags: The Setting Most People Get Wrong
Flags are the letters after the closing slash. In the tester they go in their own small field. There are only four you need.
| Flag | Name | What changes |
|---|---|---|
| g | Global | Find every match, not just the first. Without it you get one result and one highlight. |
| i | Case-insensitive | ORDERS with gi matches Orders, orders, OrDeRs. |
| m | Multiline | Makes ^ and $ mean "start/end of each line" instead of the whole text. Essential for logs. |
| s | dotAll | Lets . match newlines too. Off by default, which is why patterns spanning two lines mysteriously fail. |
You can combine them — gim is a perfectly normal setting. Type an invalid letter and the tester tells you immediately: Invalid flags supplied to RegExp constructor 'q'.
The dotAll trap, demonstrated. Against this three-line sample:
Orders: A-1042, B-99, C-10429
Emails: ana.lee+x@shop.co.uk, bo@y.org
Called 604-555-0142 on 2026-08-03.
Emails.+Calledwith flagsg→ no matches. The.refuses to cross the newline.- The same pattern with flags
gs→ matchesEmails: ana.lee+x@shop.co.uk, bo@y.org\nCalled.
Nothing was wrong with the pattern. One letter of configuration was.
Patterns You Will Actually Use
Every result below was produced by running the pattern against the sample text from the previous section. Paste that sample into the tester and reproduce them yourself.
Find every email address
[\w.+-]+@[\w-]+\.[\w.]+ flags: g
→ ["ana.lee+x@shop.co.uk", "bo@y.org"]
Read it left to right: one or more word characters, dots, plus signs or hyphens; an @; the domain; an escaped dot; the extension. This is a finder, not a validator — see the FAQ on validating emails before you use it in a signup form.
Find phone numbers
\d{3}-\d{3}-\d{4} flags: g
→ ["604-555-0142"]
Find ISO dates
\d{4}-\d{2}-\d{2} flags: g
→ ["2026-08-03"]
Pull out reference codes
[ABC]-\d+ flags: g
→ ["A-1042", "B-99", "C-10429"]
Find the start of each line in a log
^\w+: flags: gm
→ ["Orders:", "Emails:"]
Drop the m and you get only Orders: — ^ would mean the start of the entire text.
Find trailing whitespace (the invisible bug)
+$ flags: gm
→ [" ", " "]
Two lines with trailing spaces you could not see. This one pattern has saved more "why doesn't my comparison match" debugging sessions than any other. If you are chasing a difference between two files, pair it with Text Compare — the file comparison guide covers the whitespace traps in detail.
Capture Groups: Extracting, Not Just Finding
Parentheses do two jobs. They group things so a quantifier applies to the whole group, and they capture — remembering each piece separately so you can pull it out.
(\d{4})-(\d{2})-(\d{2}) against "2026-08-03", no g flag
→ ["2026-08-03", "2026", "08", "03"]
Position 0 is the whole match. Positions 1, 2 and 3 are the year, month and day — separately, ready to use. That is how you turn 2026-08-03 into 03/08/2026 in a single find-and-replace, in any editor that supports regex:
Find: (\d{4})-(\d{2})-(\d{2})
Replace: $3/$2/$1
Result: 03/08/2026
Named groups make long patterns readable: (?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2}) gives you match.groups.year in code. The names are for your benefit — the numbered positions still work exactly the same.
If you only want to group and not capture, use (?:...). It keeps your numbered positions clean.
Important behaviour to know before it confuses you: the g flag and capture groups do not coexist in a simple match. With g, you get a flat list of every full match and the groups are discarded. Without g, you get the first match plus its groups. Same pattern, two completely different result shapes:
Text: ana@x.com and bo@y.org
Pattern: ([\w.]+)@([\w.]+)
flags g → ["ana@x.com", "bo@y.org"] 2 matches, no groups
flags — → ["ana@x.com", "ana", "x.com"] 1 match + its 2 groups
This is standard JavaScript behaviour, not a quirk of any particular tester. In code, use matchAll() when you want every match and its groups.
The Greedy Trap (Everyone Hits This Once)
Quantifiers are greedy by default: they grab as much as they possibly can and only give characters back if the rest of the pattern fails. This produces the single most common regex bug.
Text: <b>bold</b>
<.+> flags: g → ["<b>bold</b>"] one match — the whole line
<.+?> flags: g → ["<b>", "</b>"] two matches — the tags
You almost certainly wanted the second. Adding ? after a quantifier makes it lazy — take as little as possible. +?, *? and {2,}? all work the same way.
The cleaner fix is often to stop using . at all. Instead of "anything, lazily", say what you actually mean: <[^>]+> reads as "a bracket, then one or more characters that are not a closing bracket, then a closing bracket". It cannot overshoot because it is structurally incapable of crossing a >.
The other half of the trap: unescaped dots
. means "any character". A literal full stop is \.. Against the text 2x14 and 3.9:
\d+.\d+ flags: g → ["2x14", "3.9"] the x matched the dot
\d+\.\d+ flags: g → ["3.9"] correct
The characters that need escaping when you want them literally: . ^ $ * + ? ( ) [ ] { } | \ /. Inside square brackets most of them lose their power and can be written plainly.
Anchors and Word Boundaries
Anchors match a position, not a character. They consume nothing and they are how you stop a pattern matching inside the middle of something else.
^— start of the text, or start of each line with themflag$— end of the text, or end of each line withm\b— a word boundary: the invisible seam between a word character and a non-word character
\b is the one people underuse. Against cat category concat cat.:
cat flags: g → ["cat", "cat", "cat", "cat"] 4 — including inside "category" and "concat"
\bcat\b flags: g → ["cat", "cat"] 2 — the standalone words only
It works for codes too. Against A-1042, B-99, C-10429:
[A-C]-\d{4} flags: g → ["A-1042", "C-1042"] grabbed 4 of C's 5 digits
\b[A-C]-\d{4}\b flags: g → ["A-1042"] exactly four digits, as asked
Without the boundaries, "exactly four digits" silently became "at least four digits". This is the kind of error that passes every test you thought to write and then mangles one row in production.
Lookaheads (When You Need a Condition, Not a Match)
A lookahead checks that something follows without consuming it. (?=...) is "must be followed by", (?!...) is "must not be followed by".
Their most common real use is validating that a string contains several things in any order. Password rules, for example:
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$
"Passw0rdX" → ["Passw0rdX"] matched
"password" → null no uppercase, no digit
Read it as a checklist rather than left-to-right text: somewhere there is a lowercase letter, somewhere an uppercase letter, somewhere a digit, and the whole thing is at least eight characters. Each lookahead peeks from the start and then hands the position back untouched, which is why they stack.
Two honest notes. First, "contains a symbol" rules are worse password policy than length — the strong password guide covers what actually helps. Second, lookbehinds ((?<=...)) exist and work in modern browsers, Node and Python, but support elsewhere is patchier than lookaheads.
Cheat Sheet
| Pattern | Meaning |
|---|---|
| . | Any character except newline (any character with the s flag) |
| \d \D | Digit / not a digit |
| \w \W | Word character (a–z, A–Z, 0–9, _) / not one |
| \s \S | Whitespace / not whitespace |
| [abc] | Any one of a, b, c |
| [^abc] | Any character except a, b, c |
| [a-z0-9] | Ranges inside a class |
| * | Zero or more |
| + | One or more |
| ? | Zero or one (optional) |
| {3} {2,5} {8,} | Exactly 3 / between 2 and 5 / 8 or more |
| +? *? {2,}? | Lazy versions — match as little as possible |
| ^ $ | Start / end (of line with the m flag) |
| \b \B | Word boundary / not a word boundary |
| a|b | a or b |
| ( ) | Capture group — referenced as $1, $2 |
| (?: ) | Group without capturing |
| (?<name> ) | Named capture group |
| (?= ) (?! ) | Lookahead: must / must not be followed by |
| \. \\ \$ | Escaped literal characters |
Copy-and-adapt starters, all confirmed working in the tester:
Email (finder) [\w.+-]+@[\w-]+\.[\w.]+
ISO date \d{4}-\d{2}-\d{2}
NA phone \d{3}-\d{3}-\d{4}
Hex colour #[0-9a-fA-F]{6}\b
IPv4 (loose) \b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b
URL (loose) https?://[^\s]+
HTML tag <[^>]+>
Trailing whitespace +$ (use the m flag)
Blank lines ^\s*$ (use the m flag)
Duplicate word \b(\w+)\s+\1\b (use the i flag)Common Mistakes
- Forgetting the
gflag. You get one match and assume the pattern is broken. It isn't — it stopped after the first hit, as instructed. - Using
.when you mean a literal dot.3.9and3x9both match\d.\d. Escape it:\.. - Greedy
.+swallowing the line. Use.+?, or better, a negated class like[^>]+. - Expecting
.to cross newlines. It doesn't, unless you add thesflag. - Expecting
^to mean "start of line". It means start of the whole text until you addm. - Omitting
\b. "Exactly four digits" quietly becomes "at least four digits", and one row in ten thousand comes out wrong. - Parsing HTML or nested structures with regex. Regex cannot count nesting depth. Extracting one attribute from a known-shaped line is fine; parsing a document is not. Use a parser.
- Nesting quantifiers like
(a+)+. On non-matching input, backtracking can explode combinatorially and hang the page or the server. It is a real denial-of-service class (ReDoS). If a pattern makes the tester feel sluggish, simplify it rather than shipping it. - Testing against tidy sample data. Test against the messy rows — the missing field, the double space, the unicode name, the trailing comma. Those are the rows that break in production.
Where the Same Pattern Works
Regex is a portable skill. The syntax in this guide is JavaScript-flavoured, and it transfers almost unchanged to:
- VS Code, Sublime, Notepad++ — tick the
.*icon in find-and-replace, then use$1or\1in the replace field - grep -E, ripgrep, sed, awk — command-line search across whole directories
- Python —
re.findall(r'\d{4}-\d{2}-\d{2}', text) - Google Sheets / Excel —
REGEXEXTRACT,REGEXMATCH,REGEXREPLACE - SQL —
~in PostgreSQL,REGEXPin MySQL
The dialects differ at the edges — lookbehind support, named-group syntax, whether you write $1 or \1 — but the eight symbols in Section 2 behave identically everywhere. Learn them once.
Working with structured data rather than free text? Sometimes the right answer is not a regex at all: format the file first with the JSON Formatter (the JSON formatter guide covers it), or decode it with the URL Encoder/Decoder before you go pattern-matching over escaped characters.
Tips
- Build in stages. Match one component, confirm it highlights, then add the next. Never type a 40-character pattern and hope.
- Comment long patterns in code. Six months from now the pattern will be unreadable, including to you. One line above it saying what it matches costs nothing.
- Prefer negated classes to lazy dots.
[^,]+is faster and more predictable than.+?and states the intent plainly. - Keep a scratch file of test cases. Both the strings that should match and the ones that should not. Paste it back into the tester whenever you edit the pattern.
- Watch out for the tester's match counter with capture groups. With the
gflag it counts matches. Clear the flag and the count includes your capture groups too — see Section 6. - Don't paste secrets. This tester is client-side so nothing leaves your machine, but that is not true of most online testers. Check before you paste a token or a customer export anywhere.
- Know when to stop. If a pattern needs more than about 60 characters, a small parsing function is usually cheaper to maintain than the regex.
FAQs (8)
What does regex mean? ▼
Is there a free online regex tester? ▼
Why does my regex only find the first match? ▼
g to the flags field. Without it the engine stops at the first hit by design — it is not a fault in your pattern. This is by far the most common "my regex is broken" report, and it is a one-character fix.What is the difference between greedy and lazy quantifiers? ▼
+, *) take as much text as possible and give characters back only if the rest of the pattern fails. Lazy quantifiers (+?, *?) take as little as possible. On <b>bold</b>, the greedy <.+> matches the entire string as one result, while the lazy <.+?> correctly returns the two tags. When in doubt, use a negated character class like <[^>]+> — it cannot overshoot at all.Can I use regex to validate email addresses? ▼
[\w.+-]+@[\w-]+\.[\w.]+ is ideal. For validating a signup field, a loose check for an @ with something either side, followed by a confirmation email, is more reliable and better user experience than a strict regex.Why doesn't my pattern match across two lines? ▼
. matches any character except a newline. Add the s (dotAll) flag and it will cross line breaks. Separately, if you want ^ and $ to anchor to the start and end of each line rather than the whole text, add the m (multiline) flag. They are two different flags solving two different problems, and people frequently reach for the wrong one.Is regex the same in Python, JavaScript and VS Code? ▼
$1 or \1, and how each language handles unicode. A pattern built in a JavaScript-based tester will almost always work in Python's re module and in VS Code's find-and-replace without changes.Can regex parse HTML? ▼
Ready to try it?
Use the tool right now — free, no signup, no upload.