Regex for Beginners: A Practical Guide with Real Examples
Regular expressions look like noise until you learn the pieces. A beginner's regex guide covering character classes, quantifiers, anchors and real examples.
Someone hands you a 4,000-line log file and asks for every IP address in it. Or you need every email address out of a document, deduplicated. Or you have to rename 300 files from 2026-08-03 to 03-08-2026.
You could do any of these by hand, or you could write six characters of regex and be done. That gap — between an afternoon and six characters — is why regular expressions are worth an hour of your time even if you never become fluent.
What regex is actually for
A regular expression is a pattern that describes a shape of text. Not specific text — a shape. “Four digits, a hyphen, two digits, a hyphen, two digits” is a shape. So is “any word repeated twice in a row”.
Once you can describe a shape, every tool that speaks regex can find it, extract it, replace it, or validate against it: your editor’s search box, grep, sed, JavaScript, Python, log aggregators, and most find-and-replace fields worth using. This guide uses JavaScript-flavoured regex, which is close enough to Python, PHP, Java, and Perl that everything here transfers.
Literals and metacharacters
Most characters match themselves. The pattern cat finds the letters c-a-t. That is a literal. The power comes from about a dozen metacharacters that mean something else:
. ^ $ * + ? { } [ ] \ | ( )
The most immediately useful is . — a dot matches any single character (except a newline by default). So c.t matches cat, cot, and c9t.
To match a metacharacter literally, escape it with a backslash. A real dot is \., which matters more than you would think — 3.14 matches 3x14, but 3\.14 does not.
Character classes: matching a set
Square brackets define your own set of allowed characters. [aeiou] matches any one vowel. Use ranges for runs — [a-z], [0-9], or combined as [a-zA-Z0-9] — and a ^ as the first character to negate the set, so [^0-9] means “any character that is not a digit”.
Three shorthands cover most real use:
\d— a digit. Same as[0-9].\w— a “word” character: letters, digits, and underscore.\s— whitespace: space, tab, newline.
Their uppercase versions negate: \D is any non-digit, \W any non-word character, \S any non-whitespace.
Quantifiers: matching repetition
A quantifier says how many times the thing before it may repeat.
*— zero or more+— one or more?— zero or one (optional){3}— exactly three;{2,5}— two to five;{2,}— two or more
So \d+ is “one or more digits” and colou?r matches both spellings.
Greedy vs lazy
By default quantifiers are greedy: they grab as much as they possibly can while still allowing the overall match to succeed. Adding a ? makes them lazy — take as little as possible.
The classic demonstration, on the string <b>bold</b>:
"<b>bold</b>".match(/<.+>/g) // ["<b>bold</b>"] greedy: one huge match
"<b>bold</b>".match(/<.+?>/g) // ["<b>", "</b>"] lazy: two small matches
If your pattern is matching far more than you expected, greediness is almost always the reason.
Anchors: matching a position
Anchors match a location, not a character. They consume nothing.
^— start of the string (or of a line, with themflag)$— end of the string (or line)\b— a word boundary: the edge between a\wcharacter and a non-\wcharacter
Anchors separate “contains” from “is”. \d{3} finds three digits anywhere inside abc12345; ^\d{3}$ matches only a string that is exactly three digits, which is what validation needs. And \b fixes the classic substring bug: searching for cat also hits category and concatenate, but \bcat\b does not.
Groups and alternation
Parentheses do two jobs at once.
They group, so a quantifier applies to a whole sequence: (ab)+ matches ababab, whereas ab+ matches abbb.
They capture, so you can pull out the piece they matched:
"2026-07-04".match(/^(\d{4})-(\d{2})-(\d{2})$/)
// → ["2026-07-04", "2026", "07", "04"]
Captures are numbered left to right by their opening bracket, and they are what makes find-and-replace powerful. In JavaScript you refer to them as $1, $2 in the replacement string:
"2026-07-04".replace(/^(\d{4})-(\d{2})-(\d{2})$/, "$3/$2/$1")
// → "04/07/2026"
If you only want grouping and not the capture, use (?:...) — a non-capturing group. It keeps your numbered captures clean.
Alternation is the pipe: cat|dog matches either. Combine with a group to scope it: ^(cat|dog)s?$.
Flags
Flags change how the whole pattern behaves. Three earn their keep:
g(global) — find every match, not just the first. Without it, most tools stop after one.i(case-insensitive) —/error/imatchesErrorandERROR.m(multiline) — makes^and$match at each line break rather than only at the start and end of the whole input. Essential for log files.
Four worked examples
1. Extract email addresses, loosely
[\w.+-]+@[\w-]+\.[\w.-]+
Piece by piece: [\w.+-]+ is one or more word characters, dots, plus signs, or hyphens — the local part, including Gmail-style + tags. Then a literal @, then [\w-]+ for the domain, a literal dot \., and [\w.-]+ for the rest, written to allow further dots so .co.uk survives.
Run against Mail ada@example.com or sales+eu@my-shop.co.uk it returns both addresses. If you just want addresses out of a blob of text without writing anything, our Email Extractor does this and removes duplicates.
2. Find doubled words
\b(\w+)\s+\1\b
This is the first genuinely clever one. (\w+) captures a word. \s+ matches the space after it. Then \1 is a backreference — it means “whatever group 1 just matched, again”. With the gi flags, this catches We we need to to fix this, a typo that survives every spellchecker.
3. Pull numbers out of text
-?\d+(?:\.\d+)?
-? allows an optional leading minus. \d+ takes the whole part. (?:\.\d+)? is an optional non-capturing group holding a decimal point and its digits — so integers and decimals both match. Against Total -12.50 plus 3 items at 4.99 you get -12.50, 3, 4.99.
4. Validate an ISO date
^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$
The anchors force a whole-string match. (0[1-9]|1[0-2]) restricts the month to 01–12 using alternation, and the day group allows 01–09, 10–29, and 30–31.
This correctly rejects 2026-13-01 and 2026-7-4. It accepts 2026-02-31, and that is the honest lesson: regex checks shape, not meaning. Calendar validity is a job for a date library.
A warning about clever regex
Two failure modes are worth knowing before you get enthusiastic.
Some things should not be regex at all. Fully standards-compliant email validation is the famous example — the patterns that attempt it are notoriously long, unreadable, and still reject addresses that work fine. The practical approach is a loose check that catches typos, then a confirmation email, which is the only test that proves anything anyway. Same for parsing HTML, JSON, or any nested structure: use a real parser.
Nested quantifiers can explode. A pattern like (a+)+$ can force the engine down an enormous number of paths on input that nearly matches, hanging the process. If a regex will run on user-supplied text, keep it simple and test it on hostile input.
And a softer rule: if a pattern is longer than a sentence, add a comment explaining what it does. Future you will not remember.
Quick answers
Why does my pattern only find the first match? You are missing the g flag.
What is the difference between * and +? * allows zero occurrences, + requires at least one. \d* matches an empty string; \d+ does not.
How do I match a literal dot or slash? Escape it with a backslash: \. and \/.
Why does cat match inside category? Because regex matches substrings by default. Wrap it in word boundaries: \bcat\b.
Is regex the same everywhere? The basics here work almost anywhere. Lookbehind, named groups, and Unicode handling differ between engines — test in the environment you will actually run in.
The takeaway
Regex is not one big skill; it is five small ones — literals, character classes, quantifiers, anchors, and groups. Learn those and you can read most patterns you meet and write the ones you need.
Build patterns incrementally rather than all at once: match one piece, confirm it, add the next. The Regex Tester highlights matches and capture groups live as you type, which turns the whole thing into a feedback loop instead of a guessing game, and once a pattern works you can put it to use in Find and Replace. Both run in your browser, so log files and customer data stay on your machine.