Let's be honest. Regular expressions (regex) look like alien code at first glance. ^[\w.-]+@[\w.-]+\.\w{2,}$ – what even is that? I felt the same way when I started.
But once you get the hang of it, you'll understand why developers keep talking about regex. There's nothing quite as powerful for text processing.
What Even Is Regex?
In one sentence: A language for finding patterns in text.
"I want to find only email addresses", "I want to validate phone number formats", "I want to extract domains from URLs"—regex solves all of these.
Starting From the Basics
1. Literal Matching
The simplest regex is just the characters you want to find.
Pattern: hello
Text: "say hello to the world"
2. Metacharacters: Characters with Special Meaning
This is where the real power of regex comes from.
| Meta | Meaning | Example |
|---|---|---|
| . | Any single character | h.t → hat, hit, hot |
| ^ | Start of string | ^Hello → strings starting with Hello |
| $ | End of string | world$ → strings ending with world |
| * | 0 or more of previous | ab*c → ac, abc, abbc, abbbc... |
| + | 1 or more of previous | ab+c → abc, abbc... (not ac) |
| ? | 0 or 1 of previous | colou?r → color, colour |
3. Character Classes: One of Multiple Characters
Square brackets [] match one character from the set inside.
[aeiou] → one vowel
[0-9] → one digit
[a-zA-Z] → one letter
[^0-9] → NOT a digit (^ inside means "exclude")
4. Predefined Character Classes
Common patterns have shortcuts.
| Shortcut | Meaning | Equivalent |
|---|---|---|
| \d | Digit | [0-9] |
| \w | Word character | [a-zA-Z0-9_] |
| \s | Whitespace | Space, tab, newline |
| \D, \W, \S | Uppercase = opposite | Not digit, not word char... |
Practical Examples
Email Validation (Simple)
[\w.-]+@[\w.-]+\.\w{2,}
Breaking it down:
[\w.-]+: word chars, dots, hyphens (1+) — the username part@: literal @ symbol[\w.-]+: domain name\.: literal dot (escaped)\w{2,}: 2+ word chars (com, net, org, etc.)
US Phone Number
\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}
Matches (123) 456-7890, 123-456-7890, 123.456.7890, and more.
Strip HTML Tags
<[^>]+>
Starts with <, has one or more non-> characters, ends with >. Matches all HTML tags.
Common Mistakes
1. Forgetting to escape
Dots (.), asterisks (*), parentheses have special meanings. To match them literally, prefix with \. To find 1+1=2, use 1\+1=2
2. Greedy matching
.* matches as much as possible. <.*> on <b>hi</b> matches the entire string, not just <b>. Add ? for lazy matching: <.*?>
3. Missing anchors
\d{3} finds "3 digits", but matches inside "12345" too. For exactly 3 digits, use anchors: ^\d{3}$
Quantifiers: Controlling Repetition
Beyond *, +, and ?, regex offers precise repetition control with curly braces.
| Quantifier | Meaning | Example |
|---|---|---|
| {3} | Exactly 3 | \d{3} matches "123" but not "12" |
| {2,5} | Between 2 and 5 | a{2,5} matches "aa", "aaa", up to "aaaaa" |
| {3,} | 3 or more | x{3,} matches "xxx", "xxxx", etc. |
Greedy vs Lazy Matching
By default, quantifiers are greedy: they match as much text as possible. Adding a ? after a quantifier makes it lazy, matching as little as possible.
This distinction matters most when your pattern contains wildcards. Consider the text <b>bold</b> and <i>italic</i>:
<.*> (greedy) matches: <b>bold</b> and <i>italic</i>
<.*?> (lazy) matches: <b>, </b>, <i>, </i>
The greedy version grabs everything from the first < to the last >. The lazy version stops at the nearest >. In most real-world HTML processing, lazy is what you want.
Capture Groups and Backreferences
Parentheses () do two things in regex: they group expressions, and they capture matched text for later use. The captured text can be referenced by number (\1, \2, etc.) within the same pattern or in replacement strings.
// Find repeated words
\b(\w+)\s+\1\b
Matches: "the the the quick fox"
Here, (\w+) captures a word, \s+ matches whitespace, and \1 refers back to whatever the first group captured. This pattern finds accidental duplicate words, something many text editors use for spell-checking.
Named groups make complex patterns more readable. In JavaScript: (?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2}). You can then access match.groups.year instead of remembering that year is group 1.
If you need grouping without capturing (to avoid polluting your group numbering), use non-capturing groups: (?:pattern).
Lookahead and Lookbehind
Lookaround assertions let you match text based on what comes before or after it, without including that context in the match. They are "zero-width" because they do not consume characters.
| Syntax | Name | Meaning |
|---|---|---|
| (?=...) | Positive lookahead | Followed by ... |
| (?!...) | Negative lookahead | NOT followed by ... |
| (?<=...) | Positive lookbehind | Preceded by ... |
| (?<!...) | Negative lookbehind | NOT preceded by ... |
A practical example: you want to find prices in dollars but extract only the number, not the dollar sign.
(?<=\$)\d+(\.\d{2})?
Text: "Price: $29.99" matches 29.99
The lookbehind (?<=\$) asserts that a dollar sign precedes the match, but the dollar sign itself is not part of the captured result.
Five Practical Patterns You Will Actually Use
1. URL Extraction
https?://[^\s<>"']+
Matches both http and https URLs. The character class [^\s<>"'] matches everything that is not whitespace or common delimiters, stopping at natural URL boundaries.
2. IPv4 Address
\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b
This is the simple version. It matches the format but does not validate that each octet is 0-255. For strict validation, you need a longer pattern with alternation, but for log file extraction this simple version works well.
3. Date Format (YYYY-MM-DD)
\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])
This validates ISO 8601 date format with some bounds checking. Months are restricted to 01-12 and days to 01-31. It does not catch invalid dates like February 31, but regex is rarely the right tool for full date validation anyway.
4. Password Strength Check
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$
Uses four lookaheads to assert the presence of a lowercase letter, uppercase letter, digit, and special character, all without consuming characters. The final part matches the actual password (8 or more characters). This is a classic use case for lookaheads in validation.
5. Extract Quoted Strings
"([^"\\]|\\.)*"
Matches a double-quoted string, correctly handling escaped quotes inside. The alternation [^"\\]|\\. means "either a normal character or a backslash followed by anything." This handles cases like "He said \"hello\"" correctly.
Performance Tips and Catastrophic Backtracking
Most regex performance problems come from a phenomenon called catastrophic backtracking. It occurs when a pattern has nested quantifiers that can match the same characters in multiple ways. The regex engine tries every possible combination before giving up, and the number of combinations grows exponentially with input length.
The classic example is (a+)+ applied to a string of a characters followed by something that does not match. With 25 a characters, the engine may try over 33 million paths. With 30, it takes minutes. With 40, it never finishes.
To avoid this:
- Be specific: Use
[a-z]+instead of.+when you know what characters to expect. - Use atomic groups or possessive quantifiers when available. In Java and PCRE,
(?>a+)ora++prevents backtracking into the group. - Avoid nested quantifiers: Patterns like
(a*)*or(a+)+are almost always a mistake. - Test with long inputs: A pattern that works fine on 20 characters might hang on 100. Always test edge cases.
- Set timeouts: In production code, always set a timeout on regex execution. JavaScript's
RegExpdoes not support this natively, but libraries like RE2 (used by Go) guarantee linear-time matching.
A Brief History
Regular expressions trace back to mathematician Stephen Kleene, who formalized "regular sets" in 1956 as part of his work on nerve net models. Ken Thompson brought them into computing in the 1960s by implementing regex in the QED text editor, then in the Unix tool grep (which stands for "global regular expression print"). Perl popularized extended regex syntax in the 1980s and 1990s, adding features like backreferences and lookaheads that go beyond the mathematical definition of regular languages. Today, nearly every programming language includes a regex engine, though the details of syntax and supported features vary.
Practice Makes Perfect
Regex is learned by doing, not reading. Use the tool below to test patterns in real-time. Seeing matches instantly as you type makes learning much faster. Start with simple patterns and gradually add complexity. Before long, those alien-looking strings will start making sense.
Frequently Asked Questions
What is the best way to learn regex?
Practice with real problems. Start with simple patterns like email validation or phone number extraction. Use online testers to see results in real time. Don't try to memorize all syntax - look it up when needed.
When can regex be slow?
Nested quantifiers like (a+)+ can cause catastrophic backtracking. Complex patterns on large inputs can also be slow. Use specific patterns and minimize greedy quantifiers like .* where possible.