Regex Basics: How to Test and Debug Regular Expressions
Learn regex step by step: literals, character classes, quantifiers, anchors, groups, and alternation. Test patterns instantly with our free regex tester tool.
What Regex Does
A regular expression is a pattern that matches text. You describe what you want to find: digits, letters, a specific sequence, a repeating pattern. The regex engine finds all matches in the input. Regex is used for validation ("is this a valid email?"), extraction ("get all URLs from this text"), and transformation ("replace all dates from MM/DD/YYYY to YYYY-MM-DD").
Every developer uses regex. Some love it. Some fear it. The difference is usually just whether you have a good way to test patterns as you write them.
Building Blocks
Literal Characters
Letters and digits match themselves. The regex cat matches the literal text
"cat" anywhere in the input. No special syntax needed.
Pattern: cat
Input: The cat sat on the catalog
Matches: "cat" in "cat", "cat" in "catalog"
The regex engine finds "cat" as a substring inside "catalog". If you want only
the word "cat" and not "catalog", add word boundaries: \bcat\b.
Character Classes
Square brackets match any single character from a set:
[aeiou] → any vowel
[a-z] → any lowercase letter
[0-9] → any digit
[A-Za-z] → any letter (upper or lowercase)
[^0-9] → anything that is NOT a digit
[a-zA-Z0-9_] → word character (same as \w)
Character classes are case-sensitive. [a-z] matches lowercase only. [A-Z]
matches uppercase only. [a-zA-Z] matches both.
You can combine ranges and individual characters: [aeiou0-9] matches any
vowel or any digit. The hyphen is literal when placed at the start or end:
[-abc] matches hyphen, a, b, or c.
Shorthand Classes
Common character classes have shorthand:
\d → digit [0-9]
\w → word character [A-Za-z0-9_]
\s → whitespace (space, tab, newline)
\D → NOT a digit
\W → NOT a word character
\S → NOT whitespace
. → any character except newline
These are case-inverted pairs: \d / \D, \w / \W, \s / \S. The
uppercase version matches everything the lowercase version does not.
Quantifiers
How many of the preceding token:
a* → zero or more 'a' characters (greedy)
a+ → one or more 'a' characters
a? → zero or one 'a' (optional)
a{3} → exactly three 'a' characters
a{2,4} → two to four 'a' characters
a{2,} → two or more 'a' characters
a*? → zero or more, lazy (as few as possible)
a+? → one or more, lazy
Greedy vs. lazy is critical. a.*b on "a1b2b" matches the entire string
(a1b2b) because .* consumes as much as possible. a.*?b matches only
a1b because .*? stops at the first b.
Anchors
Where in the string to match:
^ → start of string (or line with m flag)
$ → end of string (or line with m flag)
\b → word boundary
\B → NOT a word boundary
^Hello matches "Hello" only at the start. world$ matches only at the end.
\bcat\b matches "cat" but not "catalog" or "scatter".
Anchors are zero-width: they match a position, not a character. ^ doesn't
consume the first character. It asserts that the position is at the start.
Groups and Alternation
(abc) → capturing group (captures "abc" for backreference)
(?:abc) → non-capturing group (no backreference)
a|b → alternation (matches "a" or "b")
(foo|bar) → matches "foo" or "bar"
Capturing groups save the matched text for later use with \1, \2 (in the
pattern) or $1, $2 (in replacement). Non-capturing groups (?:...) group
without saving, which is slightly faster and avoids polluting the capture list.
Lookahead and Lookbehind
(?=...) → positive lookahead (followed by ...)
(?!...) → negative lookahead (not followed by ...)
(?<=...) → positive lookbehind (preceded by ...)
(?<!...) → negative lookbehind (not preceded by ...)
Lookarounds are also zero-width: they check conditions without consuming input.
\d+(?=\.) // digits followed by a period
(?<=\$)\d+ // digits preceded by a dollar sign
Not all regex engines support lookbehind. JavaScript added lookbehind in ES2018. GNU grep does not support it.
Testing and Debugging Workflow
The Regex Tester gives instant visual feedback. The workflow:
- Type your pattern.
- Type or paste your test string.
- Matches highlight in real time.
- Iterate: add a quantifier, change a character class, see what changed.
This is dramatically faster than writing a pattern, running a script, getting a boolean result, and guessing why it didn't match. Visual feedback closes the debug loop from minutes to seconds.
Step-by-Step Debugging Example
You want to extract all dollar amounts from text: "$19.99", "$1,234.56", etc.
Start simple:
Pattern: \$\d+
Input: "The price is $19.99 and shipping is $5"
Matches: $19, $5
The \d+ stops at the period. You need the cents.
Pattern: \$\d+\.\d{2}
Input: "The price is $19.99 and shipping is $5"
Matches: $19.99
Now $5 doesn't match because it has no cents. Make the decimal optional:
Pattern: \$\d+(?:\.\d{2})?
Input: "The price is $19.99 and shipping is $5"
Matches: $19.99, $5
Better. Now handle commas in thousands:
Pattern: \$\d{1,3}(?:,\d{3})*(?:\.\d{2})?
Input: "The price is $1,234.56, shipping $5, and tax $0.99"
Matches: $1,234.56, $5, $0.99
This iterative approach . start simple, test, add complexity . is the fastest way to build correct regex. The alternative (writing the complete pattern in one shot and debugging it as a whole) takes far longer.
Common Patterns and Their Meanings
| Pattern | Matches |
|---|---|
| \d{3}-\d{2}-\d{4} | SSN format: 123-45-6789 |
| \b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b | Most email addresses |
| https?://[^\s]+ | URLs starting with http or https |
| \(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4} | US phone numbers (various formats) |
| \d{4}-\d{2}-\d{2} | ISO date: 2026-08-05 |
| ^#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$ | Hex color codes: #fff, #1a2b3c |
| \b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b | IPv4 addresses (basic) |
Common Mistakes
- Forgetting to escape special characters:
.matches any character.\.matches a literal period. In code strings, the backslash needs escaping:"\\."in Java or JavaScript,r"\."in Python raw strings. - Greedy vs lazy quantifiers:
.*is greedy and matches as much as possible..*?is lazy and stops early. Use lazy quantifiers when extracting content between delimiters. - Not anchoring:
\d+matches "123" in "abc123def".^\d+$matches only strings that are entirely digits. Anchor with^and$when validating input unless partial matches are intentional. - Assuming
.matches newlines: by default,.does not match\n. Use thes(dotall) flag or[\s\S]to match everything including newlines. - Catastrophic backtracking: nested quantifiers like
(a+)+bcan hang on non-matching input. The engine tries every split of theas between the inner and outer quantifier.
Try it yourself: open the Regex Tester. Enter
\d{3}-\d{2}-\d{4}as the pattern andMy SSN is 123-45-6789as the test string. The tester highlights the match. Add a second SSN to the test string and observe both matches. Modify the pattern to\b\d{3}-\d{2}-\d{4}\bto require word boundaries. Then try building a dollar-amount pattern from scratch, starting with\$\d+and adding decimal and comma support.