Developer

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.

Try it now: Regex Tester Test regex with live highlighting — free, no signup, runs in your browser.

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 the m flag)
  • $ — end of the string (or line)
  • \b — a word boundary: the edge between a \w character and a non-\w character

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/i matches Error and ERROR.
  • 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 [email protected] or [email protected] 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.

Tools mentioned in this guide

More developer guides

All guides
Developer What Is Base64 Encoding? A Complete Beginner's Guide Base64 turns binary data into safe, printable text. Learn how it works, when to use it, why it is not encryption, and how to encode or decode it in seconds. Developer URL Encoding Explained: Percent-Encoding, Query Strings and Common Bugs Why URLs break on spaces, ampersands and accents — and how percent-encoding fixes it. Covers encodeURI vs encodeURIComponent, + vs %20, and double encoding. Developer MD5 vs SHA-256: Which Hash Function Should You Use? MD5 is fast but broken; SHA-256 is the sensible default. Learn how hash functions work, why hashing is not encryption, and how to hash passwords properly.