By Todd L. · Published 2026-07-15 · 8 min read
Regular Expressions (Regex) for Beginners
Regular expressions — commonly called regex — are one of the most powerful text-processing tools available. They let you search, match, and manipulate text using concise pattern descriptions. While the syntax can look intimidating at first glance, regex is built from a small set of fundamental concepts that, once understood, open up enormous possibilities.
What Is Regex?
A regular expression is a sequence of characters that defines a search pattern. Think of it as a more powerful version of the search function you use every day. Instead of searching for exact text, you can search for patterns — like "any email address," "any phone number," or "any word that starts with A and ends with Z."
Regex is used everywhere: text editors (find and replace), programming languages (string processing), command-line tools (grep), web forms (input validation), databases (pattern matching in queries), and data analysis (extracting information from unstructured text).
Basic Building Blocks
Literal characters match themselves. The regex hello matches the text "hello" exactly. Most characters in regex are treated as literal unless they have special meaning.
Metacharacters are characters with special meaning:
.— matches any single character (except newline):h.tmatches "hat", "hot", "h3t"\d— matches any digit (0-9):\d\dmatches "42" but not "ab"\w— matches any word character (letters, digits, underscore)\s— matches whitespace (space, tab, newline)^— matches the start of a line$— matches the end of a line
Quantifiers: How Many
Quantifiers specify how many times a character or group should appear:
*— zero or more times:go*dmatches "gd", "god", "good", "goood"+— one or more times:go+dmatches "god", "good" but not "gd"?— zero or one time (optional):colou?rmatches both "color" and "colour"{n}— exactly n times:\d{3}matches exactly 3 digits{n,m}— between n and m times:\d{2,4}matches 2, 3, or 4 digits
Character Classes: Matching Types
Character classes let you match one character from a set of possibilities:
[abc]— matches a, b, or c[a-z]— matches any lowercase letter[A-Za-z]— matches any letter[0-9]— matches any digit (same as \d)[^abc]— matches anything except a, b, or c (negation)
Predefined classes provide shortcuts: \d for digits, \w for word characters, \s for whitespace. Their uppercase counterparts (\D, \W, \S) match the opposite.
Groups and Alternation
Parentheses create groups, and the pipe character | provides alternation (OR):
(cat|dog)— matches "cat" or "dog"(\d{3})-(\d{4})— matches phone number patterns like "555-1234" and captures area code and number separately(?:...)— non-capturing group: groups without saving the match for later use
Practical Examples
Email validation (simplified):
[\w.+-]+@[\w-]+\.[\w.]+ — matches most common email formats
Phone numbers (US format):
\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4} — matches formats like (555) 123-4567, 555-123-4567, 555.123.4567
Date validation (MM/DD/YYYY):
\d{2}/\d{2}/\d{4} — matches 01/15/2024 but not 1/5/24
URLs:
https?://[\w.-]+(?:/[\w./-]*)? — matches http:// and https:// URLs
HTML tags:
</?[\w]+[^>]*> — matches opening and closing HTML tags
Common Pitfalls
- Greedy vs lazy matching —
.*matches as much as possible (greedy);.*?matches as little as possible (lazy) - Escaping special characters — use
\.to match a literal period,\\for a literal backslash - Case sensitivity — most regex engines are case-sensitive by default; use flags like /i for case-insensitive matching
- Anchors matter — without ^ or $, the pattern can match anywhere in the text, not just at the beginning or end
Testing Your Regex
Our Regex Tester lets you write and test regex patterns in real time. Enter your pattern and test string, and it highlights matches instantly, showing you exactly what your regex captures. This is invaluable for debugging patterns before using them in code or applications.
Building a Pattern Step by Step: US ZIP Codes
The fastest way to learn regex is to watch one pattern grow from a rough idea into a finished expression. Take US ZIP codes. A basic ZIP is five digits, and the extended form adds a hyphen and four more digits. Start with the five-digit core: \d{5}. That matches "90210" and also matches the first five digits of "90210-1234", which is a problem if you want the whole code.
Add the optional extension with a group and a question mark: \d{5}(-\d{4})?. The ? makes the whole -\d{4} group optional, so both "90210" and "90210-1234" match. Now anchor it. Without ^ and $, the pattern happily matches the "90210" buried inside "order 90210 today". The finished pattern is ^\d{5}(-\d{4})?$, and it rejects "9021", "90210-123", and "abcde" while accepting both valid forms.
Lookahead and Lookbehind: Peeking Without Consuming
Lookarounds check what sits next to a position without including it in the match. A positive lookahead, (?=...), requires that the text ahead matches. The pattern \d+(?=px) matches the "12" in "12px" but leaves the "px" alone, so it also matches "12" in "12px wide" without grabbing the space after it.
Negative lookahead, (?!...), requires that the text ahead does not match. The pattern foo(?!bar) matches "foo" in "foobaz" but not in "foobar". Lookbehind works the same way in reverse: (?<!\$)\d+ matches a number that is not preceded by a dollar sign, which is handy for finding prices written without the symbol.
Flags Change the Meaning
Most engines let you append flags after the closing slash, and each one alters how the pattern behaves. The i flag makes matching case-insensitive, so hello with /i finds "Hello", "HELLO", and "hElLo". The m flag makes ^ and $ match at the start and end of every line instead of only the whole string, which matters when you scan a multi-line log file. The g flag tells the engine to keep going after the first match and return every hit, not just one.
A fourth flag, s, lets the dot match newline characters. Without it, .* stops at the end of a line, which is usually what you want. Turn on s only when you genuinely need to span across lines, because it also makes greedy patterns much easier to over-match.
When Regex Gets Slow: Catastrophic Backtracking
A pattern can be correct and still hang your program. The classic offender is (a+)+$ run against a long string of a's followed by one character that does not match. The engine has to try every possible way to split the a's between the inner and outer quantifiers, and the number of combinations grows exponentially with the length of the string. Thirty a's and a trailing "b" can force millions of attempts; a few hundred a's can stall the engine for minutes.
The fix is usually to simplify. Nested quantifiers like (a+)+ or (a*)* are the danger zone, so rewrite them as a single quantifier when you can. Some engines offer atomic groups or possessive quantifiers that give up backtracking entirely, and a timeout on the match call is a reasonable safety net for untrusted input.
Reading Someone Else's Regex
When you meet a dense pattern in someone else's code, read it from left to right and name each piece out loud. Take ^(?=.*[A-Z])(?=.*[0-9]).{8,}$, a common password rule. The ^ pins the start. The first lookahead, (?=.*[A-Z]), demands an uppercase letter somewhere ahead. The second, (?=.*[0-9]), demands a digit. Then .{8,} requires at least eight characters, and $ pins the end. Each chunk is simple; the pattern only looks scary because the chunks are stacked.
This is where the TL3 Regex Tester earns its keep. Paste the pattern in, feed it a few sample strings, and the match highlighting shows you exactly which chunk matched where. It does not teach you regex, but it shows you what a pattern actually does faster than staring at the code, and that makes it a good companion while you learn.
Key Takeaways
Regex may look complex, but it is built from simple, composable pieces. Start with literal characters, add quantifiers for repetition, use character classes for flexibility, and group patterns with parentheses. With practice, you will find yourself reaching for regex whenever you need to search, validate, or transform text.