Regular Expressions: The Complete Guide
Regular expressions (often abbreviated as regex or regexp) represent one of the most powerful and universally adopted tools in a software developer’s arsenal. At first glance, a regex pattern looks like a chaotic string of random punctuation marks. However, once you learn the syntax, regex becomes an irreplaceable superpower that allows you to search, match, validate, and transform large blocks of text with surgical precision.
Whether you are validating user email inputs on a frontend form, parsing terabytes of Nginx access logs on a backend server, executing complex refactoring operations across a large codebase, or building web scrapers, regular expressions are the underlying technology making it possible.
This comprehensive guide will take you from absolute basics to advanced engine mechanics, covering syntax, quantifiers, capture groups, zero-width assertions (lookaheads/lookbehinds), and the dangerous phenomenon of catastrophic backtracking that can crash your servers.
Practice as you read: Open our interactive Regex Tester in another tab. Paste the patterns from this guide and test them against your own sample text to see how they behave in real-time.
What Is a Regular Expression?
At its core, a regular expression is a sequence of characters that defines a specific search pattern. Think of it as a highly advanced “Find and Replace” feature that operates on rules rather than exact string matches.
Pattern: \b\d{3}-\d{4}\b
Matches: "555-1234", "800-9999"
Does not match: "5551234", "A55-1234", "555-12345"
The pattern above tells the engine: “Find a word boundary, followed by exactly three digits, followed by a literal hyphen, followed by exactly four digits, ending with another word boundary.”
Basic Syntax and Literal Characters
The simplest regular expression is just a string of literal characters. The pattern hello matches the exact sequence of letters “h-e-l-l-o” in the target text.
However, the true power of regex comes from Metacharacters — special characters that have a dedicated meaning within the regex engine.
Essential Metacharacters
| Metacharacter | Meaning | Example Pattern | What It Matches | What It Doesn’t Match |
|---|---|---|---|---|
. | Any single character (except a newline) | h.t | hat, hit, hot, h5t | ht, hoot |
\d | Any digit (0-9) | \d\d | 42, 99, 00 | 4a, a9, 4 |
\w | Any word character (a-z, A-Z, 0-9, and underscore _) | \w\w | a1, _B, ZZ | a!, a-, a |
\s | Any whitespace character (space, tab, newline) | a\sb | a b, a\tb | a-b, ab |
\b | Word boundary (the position between a word and a non-word) | \bcat\b | ”cat” (as an isolated word) | “category”, “tomcat” |
^ | Start of a string (or start of a line in multiline mode) | ^Error: | ”Error: File not found" | "System Error: crash” |
$ | End of a string (or end of a line in multiline mode) | failed$ | ”Login failed" | "failed to login” |
Character Classes (Sets)
If you want to match a specific set of characters, you use square brackets [] to define a Character Class.
| Syntax | Meaning | Example Matches |
|---|---|---|
[aeiou] | Matches any single vowel | ”a”, “e”, “i” |
[a-z] | Matches any lowercase letter from a to z | ”f”, “q”, “z” |
[A-Z0-9] | Matches any uppercase letter OR any digit | ”G”, “5”, “X” |
[^0-9] | Matches any character that is NOT a digit (Negation) | “a”, ”!”, ” “ |
Quantifiers: Controlling Repetition
Often, you don’t know exactly how many times a character will appear. Quantifiers allow you to specify the required number of repetitions.
| Quantifier | Meaning | Example Pattern | Matches |
|---|---|---|---|
* | Zero or more times | ab*c | ac, abc, abbc, abbbbbbc |
+ | One or more times | ab+c | abc, abbc (Does NOT match ‘ac’) |
? | Zero or one time (Optional) | colou?r | color, colour |
{n} | Exactly n times | \d{4} | 2026, 1999 |
{n,} | n or more times | \w{3,} | cat, doggy, elephant |
{n,m} | Between n and m times | \d{2,4} | 42, 123, 2026 |
Greedy vs. Lazy Quantifiers
By default, quantifiers like * and + are greedy. They will consume as much text as possible while still allowing the overall pattern to match.
Imagine you are parsing HTML and want to extract the text inside a div tag:
Target string: <div>First</div> <div>Second</div>
Greedy Pattern: <div>.*</div>
Match Result: <div>First</div> <div>Second</div> (It matched the entire string!)
To fix this, you make the quantifier lazy (or reluctant) by adding a question mark ? after it. This tells the engine to match as little text as possible.
Lazy Pattern: <div>.*?</div>
Match 1: <div>First</div>
Match 2: <div>Second</div>
Capture Groups and Alternation
Alternation (OR logic)
The pipe character | acts as an OR operator.
Pattern: cat|dog|bird will match “cat”, “dog”, or “bird”.
Capture Groups
Parentheses () create capture groups. They serve two purposes:
- They group multiple characters together so a quantifier can apply to the entire group.
- They “capture” the matched text, allowing you to extract it later in your code.
Pattern: (\d{4})-(\d{2})-(\d{2})
Target: "Born on 2026-03-22"
Match: 2026-03-22
Group 1: 2026 (Year)
Group 2: 03 (Month)
Group 3: 22 (Day)
In programming languages like JavaScript or Python, these extracted groups can be accessed via array indices, making regex an incredibly powerful tool for parsing structured data logs.
Non-Capturing Groups
If you need to group characters for logic but don’t want the engine to store the result (which saves memory and improves performance), use a non-capturing group by adding ?: inside the parentheses.
Pattern: (?:http|https)://(www\.)?example\.com
This groups the protocol for alternation, but only captures the “www.” portion.
Advanced Techniques: Lookarounds
Lookarounds are zero-width assertions. They check if a pattern exists ahead or behind the current position, but they do not consume any characters and are not included in the final match result.
| Lookaround Type | Syntax | Meaning | Example Use Case |
|---|---|---|---|
| Positive Lookahead | (?=...) | ”Must be followed by” | \d+(?=px) matches “16” in “16px”, but ignores “16” in “16em” |
| Negative Lookahead | (?!...) | ”Must NOT be followed by” | foo(?!bar) matches “foo” in “foobaz”, but not in “foobar” |
| Positive Lookbehind | (?<=...) | ”Must be preceded by” | (?<=\$)\d+ matches “100” in “$100”, but ignores “100” in “€100” |
| Negative Lookbehind | (?<!...) | ”Must NOT be preceded by” | (?<!\w)cat matches “cat” but ignores “cat” inside “tomcat” |
Note: While lookaheads are universally supported, lookbehinds have historically lacked support in some older browsers (like Safari pre-2020), though modern environments fully support them.
Regex Execution Flags (Modifiers)
Flags change the global behavior of the regex engine. They are usually appended to the end of the regex literal (e.g., /pattern/g).
| Flag | Name | Effect |
|---|---|---|
g | Global | Finds all matches in the string, rather than stopping after the first match. |
i | Ignore Case | Case-insensitive matching. /a/i matches both “a” and “A”. |
m | Multiline | Changes the behavior of ^ and $. They now match the start/end of each line rather than the entire string. |
s | DotAll | Forces the . metacharacter to match newline characters (\n). By default, . stops at newlines. |
u | Unicode | Enables full Unicode property escapes and handles surrogate pairs correctly. |
Deep Dive: Engine Mechanics and Catastrophic Backtracking
While Regular Expressions are incredibly powerful, poorly written patterns can inadvertently cause Denial of Service (DoS) vulnerabilities in web applications. This is known as a ReDoS (Regular Expression Denial of Service) attack. To prevent this, developers must understand how the underlying engine operates.
NFA vs. DFA Engines
There are two primary architectures for regex engines: Deterministic Finite Automata (DFA) and Nondeterministic Finite Automata (NFA).
- DFA Engines (used in command-line tools like
greporawk) are rigid and extremely fast. They evaluate the input string exactly once, tracking all possible matches simultaneously in memory. The tradeoff is that they cannot support advanced features like capture groups or lookarounds. - NFA Engines (the standard for JavaScript, Python, PHP, Ruby, and Java) are feature-rich but operate using a “backtracking” approach.
The Mechanics of Catastrophic Backtracking
Catastrophic backtracking occurs exclusively in NFA engines. It happens when a regex contains nested quantifiers (e.g., *, +) and fails to find a match at the very end of a long input string.
Consider this notoriously vulnerable regex pattern:
^(a+)+$
This pattern commands the engine: “Find one or more ‘a’s, group them, repeat that group one or more times, and ensure it touches the end of the string.”
If you evaluate the string "aaaaaaaaaaaaaaaaaaaa!" (20 ‘a’s followed by an exclamation mark), here is what the NFA engine does:
- It greedily matches all 20 ‘a’s with the inner
a+. - It hits the
!and realizes the$(end of string) condition fails. - Because of its architecture, the engine assumes it grouped the letters wrong. It backtracks.
- It tries matching 19 ‘a’s in the first group, and 1 ‘a’ in the second group. It fails at the
!. - It tries 18 ‘a’s and two groups of 1. It fails.
- It tries 18 ‘a’s and one group of 2. It fails.
Because of the nested quantifiers (a+)+, the engine attempts to evaluate every single possible mathematical permutation of grouping those 20 letters before finally conceding that no match exists.
For 20 letters, this results in 2^20 (over 1 million) internal evaluations. Adding just 10 more ‘a’s to the input string pushes the evaluation count into the billions. This locks up the CPU thread entirely. In Node.js (which is single-threaded), a single user submitting this payload will crash the entire web server for all users.
Mitigation Strategies for ReDoS
To protect your applications from catastrophic backtracking, implement these rules:
- Never nest quantifiers: Avoid patterns like
(a+)+,(a*)*, or(a|a+)*. Rewrite them as simplified, flat patterns (e.g.,a+). - Beware of overlapping alternations:
(a|a)*is functionally identical to nested quantifiers and will cause the same backtracking explosion. - Use anchors immediately: Anchoring your patterns with
^and$prevents the engine from attempting to start the match from every single character in a 10,000-word document. - Enforce input length limits: Never run complex regex against an infinitely long user input. Trim or block inputs over a reasonable threshold (e.g., 255 characters for an email) before executing the regex
test()ormatch()function.
Common Real-World Patterns
Here are a few battle-tested regex patterns that developers use daily. Always test these against your specific edge cases using our Regex Tester.
Validating a Hex Color Code
^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$
Matches #FFFFFF, #abc, #123456. Rejects #F, #1234567, white.
Extracting a Domain Name from a URL
^(?:https?:\/\/)?(?:www\.)?([^\/]+)
Given https://www.example.com/page, captures example.com in Group 1.
Validating a Strong Password
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$
Uses lookaheads to ensure at least one lowercase, one uppercase, one digit, one special character, and a minimum length of 8.
Further Reading
- Email Validation Regex Patterns: Why It’s Harder Than You Think
- Regex Phone Number Patterns by Country
- The Ultimate Regex Cheat Sheet
- String and Text Processing Developer Guide
Validate, debug, and test your patterns safely using our completely private, browser-based Regex Tester. It highlights syntax, visualizes capture groups, and provides execution time warnings.