A Practical Guide to Regular Expressions (Regex)
10 min read · Published July 16, 2026 · Updated July 23, 2026
Contents
- What regex is actually for
- The core building blocks
- Worked pattern 1 — a simple email shape
- Worked pattern 2 — a US phone number
- Worked pattern 3 — extracting a hashtag
- Worked pattern 4 — validating a simple date format (YYYY-MM-DD)
- Common regex mistakes
- Flags worth knowing
- Frequently asked questions
- Test your patterns
A regular expression (regex) is a compact, precise language for describing a pattern in text — and once you can read one, tasks that would otherwise take dozens of lines of character-by-character code collapse into a single line. Validating that an input looks like an email address, pulling every hashtag out of a block of text, or checking that a date is formatted correctly are all single-pattern jobs once you know the syntax. This guide builds regex up from its individual building blocks, then walks through four complete, real-world patterns piece by piece, and finishes with the mistakes that catch even experienced developers off guard.
What regex is actually for
Imagine you need to find every phone number inside a large block of pasted text, or check that a username field only contains letters, numbers, and underscores before a form submits. Writing that logic manually — looping through characters, tracking state, handling every edge case — is tedious and error-prone. A regular expression describes the shape of what you're looking for once, as a single pattern, and a regex engine handles the character-by-character matching for you. That's the entire purpose of regex: pattern matching in text, applied to searching, validating, and extracting.
The core building blocks
Nearly every regex pattern you'll encounter is built from just seven ingredients. Learn these and you can read the vast majority of patterns you'll ever run into.
| Building block | Meaning | Example | Matches |
|---|---|---|---|
| Literal characters | Matches those exact characters | cat | "cat" anywhere in the text |
. | Any single character (except a newline, by default) | c.t | "cat", "cot", "c9t" |
[abc] / [a-z] | Any one character from the set or range | [aeiou] | Any single vowel |
* + ? {n,m} | Quantifiers — how many times the previous item repeats | lo+l | "lol", "loool" |
^ $ | Anchors — start and end of the string (or line) | ^Hi | Only matches "Hi" at the very start |
() | Groups part of a pattern together, and captures it | (ab)+ | "ab", "abab", "ababab" |
| | Alternation — either side matches | cat|dog | "cat" or "dog" |
Literal characters
The simplest possible pattern is just the text you're looking for, written plainly. The
pattern cat matches the three letters "c", "a", "t" appearing together, anywhere
in the string being searched.
The dot (.)
A period matches any single character — letter, digit, symbol, or space (but not,
by default, a line break). The pattern c.t matches "cat", "cot", "cut", and even
"c9t", since the dot doesn't care what the middle character actually is, only that exactly one
character is there.
Character classes: [abc] and [a-z]
Square brackets define a set of acceptable characters for a single position.
[aeiou] matches exactly one vowel. A hyphen inside brackets defines a range:
[a-z] matches any single lowercase letter, [0-9] matches any single
digit. A caret right after the opening bracket negates the set: [^0-9] matches
any single character that is not a digit.
Quantifiers: * + ? {n,m}
Quantifiers control how many times the preceding element can repeat:
*— zero or more times+— one or more times (at least one required)?— zero or one time (optional){n,m}— between n and m times, e.g.{2,4}means 2 to 4 times, and{3}means exactly 3 times
The pattern lo+l requires an "l", then one-or-more "o"s, then an "l" — matching
"lol", "loool", and "looooooool", but not "ll" (which needs at least one "o" in between).
Anchors: ^ and $
^ matches the start of the string (or line, in multiline mode) and
$ matches the end. Anchoring a pattern with both, like ^\d{3}$,
requires the entire string to consist of exactly three digits and nothing else —
without the anchors, that same pattern would also match "123abc", since it would just be
finding three digits somewhere inside the string rather than requiring the whole string to be
just those three digits.
Groups: ()
Parentheses group part of a pattern together so a quantifier can apply to the whole group
rather than just the single character before it, and they also capture that matched
text for later use. (ab)+ matches "ab" repeated one or more times as a unit —
"ab", "abab", "ababab" — which is different from ab+, where the +
would apply only to the "b", matching "a" followed by one-or-more "b"s instead ("ab", "abb",
"abbb").
Alternation: |
The pipe character means "or." cat|dog matches either "cat" or "dog".
Combined with grouping, gr(a|e)y matches either "gray" or "grey" — the
parentheses scope the alternation to just that one letter, rather than the pipe splitting the
entire pattern in half.
Worked pattern 1 — a simple email shape
^[\w.+-]+@[\w-]+\.[a-zA-Z]{2,}$
| Piece | What it matches |
|---|---|
^ | Start of the string — nothing is allowed before this pattern begins |
[\w.+-]+ | One or more word characters (letters, digits, underscore), dots, plus signs, or hyphens — the local part before the @, e.g. "jane.doe+work" |
@ | A literal "@" symbol, required exactly once |
[\w-]+ | One or more word characters or hyphens — the domain name, e.g. "my-company" |
\. | A literal dot (escaped, since a bare . would match any character) |
[a-zA-Z]{2,} | Two or more letters — the top-level domain, e.g. "com" or "co" |
$ | End of the string — nothing is allowed after the pattern ends |
This matches "jane.doe+work@my-company.com" in full. It is deliberately a shape check, not full email validation — the real specification for what counts as a valid email address (RFC 5322) is far more permissive and complex than any practical regex would want to implement, so a shape-based pattern like this one is the standard, pragmatic real-world compromise.
Worked pattern 2 — a US phone number
^\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$
| Piece | What it matches |
|---|---|
^ | Start of the string |
\(? | An optional literal opening parenthesis |
\d{3} | Exactly 3 digits — the area code |
\)? | An optional literal closing parenthesis |
[-.\s]? | An optional single separator: a hyphen, a dot, or a whitespace character |
\d{3} | Exactly 3 digits — the exchange code |
[-.\s]? | Another optional separator |
\d{4} | Exactly 4 digits — the subscriber number |
$ | End of the string |
Because every separator and both parentheses are optional (using ?), this
single pattern matches "(555) 123-4567", "555-123-4567", "555.123.4567", and "5551234567" all
with the same expression — a good demonstration of how a handful of optional quantifiers can
cover several real-world formatting variations at once.
Worked pattern 3 — extracting a hashtag
#\w+
| Piece | What it matches |
|---|---|
# | A literal "#" character |
\w+ | One or more word characters (letters, digits, underscore) immediately following it |
Unlike the previous two patterns, this one is deliberately not anchored with
^ and $, because the goal here isn't to validate that an entire
string is a hashtag — it's to find every hashtag within a larger piece of text.
Run with a "global" search flag against "Loving the #sunset and #goodvibes tonight!", this
pattern finds both "#sunset" and "#goodvibes" as two separate matches, stopping each match at
the space (which isn't a word character) right after "vibes".
Worked pattern 4 — validating a simple date format (YYYY-MM-DD)
^\d{4}-\d{2}-\d{2}$
| Piece | What it matches |
|---|---|
^ | Start of the string |
\d{4} | Exactly 4 digits — the year |
- | A literal hyphen |
\d{2} | Exactly 2 digits — the month |
- | A literal hyphen |
\d{2} | Exactly 2 digits — the day |
$ | End of the string |
This matches "2026-07-18" correctly. But it's worth being explicit about its real limitation: this pattern checks shape only, not calendar validity. The string "2026-13-45" — month 13, day 45, neither of which exists on any calendar — matches this pattern just as successfully as a genuine date does, because regex has no built-in concept of "months only go up to 12" or "day 45 doesn't exist." Catching that kind of error requires actual date-parsing logic layered on top of the regex shape check, not a more elaborate pattern — this is a genuine, common limitation of regex-based validation worth remembering any time you're tempted to validate something more meaningful than surface-level formatting.
Common regex mistakes
Greedy vs. lazy quantifiers
By default, * and + are greedy — they match as
much text as possible before backing off. This causes a specific, very common bug when
matching content between repeated delimiters. Given the text
<b>bold</b> and <i>italic</i>, the pattern
<.*> doesn't stop at the first > it finds — greedy matching
pushes all the way to the last > in the entire string, matching
<b>bold</b> and <i>italic</i> as one single match instead
of four separate tags. Adding a ? right after the quantifier makes it
lazy instead — <.*?> matches as little as possible, so it
correctly stops at the very first > it encounters, matching just
<b>, then separately </b>, then <i>,
and so on. This greedy-vs-lazy distinction is one of the most common sources of "why is my
regex matching way more text than I expected" bugs.
Forgetting to escape special characters
Several characters — including . * + ? ( ) [ ] { } ^ $ | and \
itself — have special meaning in regex and must be escaped with a backslash to match them as
literal characters. Forgetting this is an easy mistake: a pattern meant to match the literal
price "$9.99" written as $9.99 unescaped will not work as intended, because
$ means "end of string" and the bare . matches any character, not
specifically a period. The correct literal-matching version is \$9\.99, with both
special characters escaped. The same trap applies to matching a literal file extension like
".txt" — the pattern needs to be \.txt, not .txt, or it will also
match "atxt", "9txt", and any other single-character-then-"txt" combination.
Flags worth knowing
Most regex engines support flags (or "modifiers") that change how a pattern behaves without changing the pattern itself:
g(global) — find every match in the string, not just the first one.i(case-insensitive) — treat uppercase and lowercase letters as equivalent.m(multiline) — makes^and$match the start/end of each individual line within a multi-line string, rather than only the very start and end of the entire string.
Frequently asked questions
Is regex syntax identical across every programming language?
Mostly, but not entirely — the building blocks covered in this guide (character classes, quantifiers, anchors, groups, alternation) are consistent across almost every mainstream "flavor" (JavaScript, Python, PCRE used by PHP, Java, and more), but some advanced features (like certain lookbehind assertions) aren't supported identically everywhere. For everyday patterns like the ones in this guide, the syntax is effectively universal.
Can regex fully validate a real email address?
Not completely — the full email specification (RFC 5322) permits far more unusual syntax than most people realize, and a regex pattern that tried to accept every technically-valid address would be enormous and still imperfect. The practical, widely-used approach is a shape-based pattern like the one in this guide, combined with actually sending a confirmation email to verify the address works — which is the only fully reliable validation.
What's the difference between "search" and "test" in regex?
A search (or "match") operation finds and typically returns the matching text itself (useful for extraction, like pulling out hashtags). A test operation just returns true or false — whether the pattern matches anywhere in the string at all — which is what you want for simple validation, like checking whether a form field looks like a valid input before allowing submission.
Are regular expressions slow?
For the vast majority of everyday patterns and typical string lengths, no — modern regex engines are highly optimized. Performance only becomes a genuine concern with certain pathological patterns (heavily nested, ambiguous quantifiers) matched against specifically crafted long input, which can cause a runaway slowdown known as "catastrophic backtracking." This is a real, documented category of issue, but it affects a small minority of poorly constructed patterns, not regex as a general tool.
Test your patterns
Reading a regex pattern gets easier with practice, but nothing beats testing it against real sample text before relying on it. DocNectar's Regex Tester lets you enter any pattern and text side by side, highlights every match live as you type, and shows exactly which capture groups matched what — free, directly in your browser.
✓ Key takeaways
- ✓ Regex is a pattern-matching language for text — a single pattern can validate, search, or extract data across an entire string
- ✓ Seven building blocks cover most everyday patterns: literals, ".", character classes, quantifiers, anchors, groups, and alternation
- ✓ Greedy quantifiers (*, +) grab as much as possible; adding ? after them (*?, +?) makes them lazy, matching as little as possible instead
- ✓ Special characters like . and $ must be escaped with a backslash (\. and \$) to match them literally
- ✓ A simple regex can check that text looks right (shape) but cannot fully validate meaning — a date pattern can accept 2026-13-45, which is not a real date
Tools mentioned in this guide
Sources
Written by the DocNectar Team
Last updated July 2026
More guides
-
SEO Meta Tags Explained
What title tags, meta descriptions, canonical tags, and Open Graph tags actually do, with concrete length guid...
7 min read · SEO Tools
Read →
Updated Jul 23, 2026 -
Invoice Best Practices for Small Businesses
The fields every invoice needs, how payment terms affect your cash flow, and how invoices, quotations, and pur...
8 min read · Business Documents
Read →
Updated Jul 23, 2026 -
PDF Tools Explained: How to Merge, Split, Rotate, and Organize Your Files
What actually happens when you merge, split, rotate, or reorder a PDF, and practical workflows for keeping mul...
7 min read · PDF Tools
Read →
Updated Jul 23, 2026