Skip to content
D DocNectar

A Practical Guide to Regular Expressions (Regex)

10 min read · Published July 16, 2026 · Updated July 23, 2026

Contents

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 blockMeaningExampleMatches
Literal charactersMatches those exact characterscat"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 repeatslo+l"lol", "loool"
^ $Anchors — start and end of the string (or line)^HiOnly matches "Hi" at the very start
()Groups part of a pattern together, and captures it(ab)+"ab", "abab", "ababab"
|Alternation — either side matchescat|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,}$
PieceWhat 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}$
PieceWhat 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+
PieceWhat 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}$
PieceWhat 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

DN

Written by the DocNectar Team

Last updated July 2026

Favorites