A Beginner's Guide to Regular Expressions (Regex)
Regular expressions are one of the most powerful tools in a developer's toolkit for searching, matching, and manipulating text. Despite their cryptic appearance, regex patterns follow a logical set of rules that anyone can learn. This guide walks you through every fundamental concept, from simple character matching to advanced lookaheads, with practical examples you can use right away.
What Are Regular Expressions?
A regular expression (often abbreviated as regex or regexp) is a sequence of characters that defines a search pattern. This pattern can be used to match, find, and replace text in strings. Regular expressions are supported in virtually every programming language, text editor, and command-line tool.
The concept of regular expressions originated in the 1950s from the work of mathematician Stephen Cole Kleene, who formalized the concept of "regular sets" as part of automata theory. Ken Thompson later implemented regex in the Unix text editor ed, and the feature eventually made its way into grep (which stands for "global regular expression print"), sed, awk, and Perl. Today, most regex implementations are based on or compatible with Perl-Compatible Regular Expressions (PCRE).
Common use cases for regular expressions include form validation, log file analysis, search and replace operations, data extraction, syntax highlighting, and URL routing in web frameworks.
Literal Characters and Metacharacters
The simplest regex pattern is a literal string. The pattern cat matches the exact sequence "cat" wherever it appears in the text. However, regex becomes powerful through metacharacters — characters with special meaning:
.— matches any single character except a newline^— matches the start of a string (or line in multiline mode)$— matches the end of a string (or line in multiline mode)*— matches zero or more of the preceding element+— matches one or more of the preceding element?— matches zero or one of the preceding element\— escapes a metacharacter to treat it as a literal|— alternation (logical OR)()— grouping[]— character class{}— quantifier with specific count
If you need to match a literal dot, you must escape it: \.. Similarly, to match a literal asterisk, use \*. This escaping rule applies to all metacharacters.
Character Classes
Character classes (also called character sets) allow you to match any one character from a defined set. They are enclosed in square brackets.
[abc]— matches "a", "b", or "c"[a-z]— matches any lowercase letter from a to z[A-Z]— matches any uppercase letter from A to Z[0-9]— matches any digit from 0 to 9[a-zA-Z0-9]— matches any alphanumeric character[^abc]— negated class: matches any character except a, b, or c
There are also shorthand character classes that provide convenient aliases:
\d— any digit (equivalent to[0-9])\D— any non-digit (equivalent to[^0-9])\w— any word character (equivalent to[a-zA-Z0-9_])\W— any non-word character\s— any whitespace character (space, tab, newline, etc.)\S— any non-whitespace character
For example, \d{3}-\d{4} matches a pattern like "555-1234" — three digits, a hyphen, and four digits.
Quantifiers
Quantifiers specify how many times the preceding element should be matched:
*— zero or more times (greedy)+— one or more times (greedy)?— zero or one time (optional){n}— exactly n times{n,}— n or more times{n,m}— between n and m times (inclusive)
By default, quantifiers are greedy — they match as much text as possible. Adding a ? after a quantifier makes it lazy (also called reluctant), matching as little text as possible:
*?— zero or more, lazy+?— one or more, lazy??— zero or one, lazy
Consider the string "Hello" and "World". The greedy pattern ".*" matches the entire string "Hello" and "World", while the lazy pattern ".*?" matches "Hello" and "World" separately. Understanding the difference between greedy and lazy matching is critical for writing accurate patterns.
Anchors and Boundaries
Anchors do not match characters — they match positions in the string:
^— matches the start of the string (or line, with themflag)$— matches the end of the string (or line, with themflag)\b— matches a word boundary (the position between a word character and a non-word character)\B— matches a non-word boundary
For example, ^Hello only matches "Hello" at the very beginning of a string. The pattern \bcat\b matches the word "cat" but not "category" or "concatenate", because the word boundaries ensure "cat" stands alone.
Word boundaries are especially useful for whole-word searches. Without \b, searching for log would also match "blog", "catalog", and "logarithm".
Groups and Capturing
Parentheses create groups that serve two purposes: they group elements for quantifiers and alternation, and they capture the matched text for later use.
Capturing Groups
A capturing group saves the matched text so it can be referenced later. Groups are numbered from left to right starting at 1:
(\d{3})-(\d{4}) applied to "555-1234" creates group 1 = "555" and group 2 = "1234".
In replacement strings, captured groups can be referenced with $1, $2, etc. (or \1, \2 in some languages). This is invaluable for reformatting text.
Non-Capturing Groups
If you need grouping for logic but do not want to capture the text, use (?:...):
(?:https?|ftp):// groups the protocol alternatives without creating a numbered capture. This can improve performance when you have many groups but only need some of them.
Named Groups
Named groups make your patterns more readable. The syntax varies slightly by language:
- JavaScript / Python:
(?<name>...)or(?P<name>...) - Access in JavaScript:
match.groups.name - Access in Python:
match.group('name')
Example: (?<area>\d{3})-(?<number>\d{4}) lets you access the matched parts by name rather than number.
Lookaheads and Lookbehinds
Lookarounds are zero-width assertions that match a position based on what comes before or after, without including that text in the match:
(?=...)— positive lookahead: asserts that what follows matches the pattern(?!...)— negative lookahead: asserts that what follows does not match the pattern(?<=...)— positive lookbehind: asserts that what precedes matches the pattern(?<!...)— negative lookbehind: asserts that what precedes does not match the pattern
For example, \d+(?= dollars) matches "100" in "100 dollars" but not in "100 euros". The word "dollars" is checked but not included in the match result.
A practical application: (?<=\$)\d+\.?\d* matches the numeric amount after a dollar sign, like "49.99" in "$49.99", without including the dollar sign in the match.
Regex Flags
Flags modify how the regex engine interprets the pattern. The most common flags are:
g(global) — find all matches, not just the first onei(case-insensitive) — match uppercase and lowercase letters interchangeablym(multiline) — make^and$match the start and end of each line, not just the entire strings(dotAll) — make.match newline characters as wellu(unicode) — enable full Unicode support, treating surrogate pairs correctly
In JavaScript, flags are placed after the closing slash: /pattern/gi. In Python, they are passed as arguments: re.search(pattern, string, re.IGNORECASE | re.MULTILINE).
Common Regex Patterns
Here are practical patterns you can use in real-world applications:
Email Address (Basic)
[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}
This matches common email formats. Note that a truly RFC 5322-compliant email regex is extremely complex; for production use, it is better to combine a simple regex check with server-side verification.
Phone Number (US)
\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}
Matches formats like (555) 123-4567, 555-123-4567, 555.123.4567, and 5551234567.
URL
https?://[^\s/$.?#].[^\s]*
A simple pattern that matches most HTTP and HTTPS URLs. For stricter validation, consider a more detailed pattern or use a URL parser.
IPv4 Address
\b(?:\d{1,3}\.){3}\d{1,3}\b
Matches patterns like 192.168.1.1. For strict validation (ensuring each octet is 0-255), a more complex pattern is needed.
Date (YYYY-MM-DD)
\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])
Matches ISO 8601 date formats with basic month and day validation.
HTML Tag
<([a-z][a-z0-9]*)\b[^>]*>(.*?)<\/\1>
Matches simple opening and closing tag pairs with a backreference (\1) to ensure the closing tag matches the opening tag name. Remember: do not use regex for full HTML parsing.
Practical Examples
Validating a Password
A password regex that requires at least 8 characters, one uppercase letter, one lowercase letter, one digit, and one special character:
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$
This pattern uses four positive lookaheads at the start to assert the presence of each required character type before matching the overall pattern.
Extracting Data from Log Files
Given a log line like 2026-03-07 14:23:01 [ERROR] Connection timeout on server-42, you could use:
(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) \[(\w+)\] (.+)
This captures the date (group 1), time (group 2), log level (group 3), and message (group 4).
Search and Replace
To convert dates from MM/DD/YYYY to YYYY-MM-DD format, search for (\d{2})/(\d{2})/(\d{4}) and replace with $3-$1-$2. This rearranges the captured groups into the desired order.
Tips for Writing Better Regex
- Start simple and build up. Test each piece of your pattern incrementally rather than writing the entire expression at once.
- Use a regex tester. Tools like our Regex Tester let you experiment with patterns in real time and see matches highlighted.
- Be specific. Avoid overly broad patterns like
.*when a more specific character class would be more accurate and performant. - Use non-capturing groups when you need grouping but not capturing. This keeps your capture indices clean and can improve performance.
- Comment your patterns. Many languages support verbose/extended mode (the
xflag), which allows you to add comments and whitespace for readability. - Watch for catastrophic backtracking. Patterns with nested quantifiers like
(a+)+can cause exponential processing time on certain inputs. Keep your patterns efficient. - Consider alternatives. Sometimes a simple string method (
startsWith,includes,split) is clearer and faster than a regex.
Frequently Asked Questions
What is the difference between regex and glob patterns?
Glob patterns are a simpler pattern-matching syntax used primarily in shells and file systems, where * matches any characters and ? matches a single character. Regular expressions are far more powerful and expressive, supporting quantifiers, character classes, grouping, alternation, lookaheads, and backreferences. Regex is used for complex text matching and manipulation, while globs are best for simple filename matching.
Are regular expressions the same in every programming language?
Most languages support a common set of regex features based on Perl-compatible regular expressions (PCRE), but there are differences. JavaScript does not support lookbehinds in older engines (though modern engines do), Python uses a slightly different syntax for named groups, and some languages like Go use RE2 which omits backreferences for guaranteed linear-time matching. Always check your language's regex documentation for specifics.
How do I make a regex case-insensitive?
Use the i flag after the pattern. In JavaScript, write /pattern/i. In Python, pass re.IGNORECASE or re.I as the second argument to re.compile() or re.search(). In most regex testers, you can toggle case insensitivity with a checkbox or by adding (?i) at the beginning of the pattern as an inline flag.
What is the difference between greedy and lazy quantifiers?
Greedy quantifiers (*, +, {n,m}) match as many characters as possible while still allowing the overall pattern to succeed. Lazy quantifiers (*?, +?, {n,m}?) match as few characters as possible. For example, given the input "<b>bold</b>", the greedy pattern <.*> matches the entire string "<b>bold</b>", while the lazy pattern <.*?> matches just "<b>" and then "</b>" separately.
Can regular expressions parse HTML or XML?
Regular expressions are not suitable for parsing full HTML or XML documents because these are nested, recursive structures that require a context-free grammar to parse correctly. However, regex can be useful for simple, well-defined HTML tasks like extracting all href attributes from anchor tags or finding specific patterns within known markup structures. For robust HTML parsing, use a dedicated parser like DOMParser, BeautifulSoup, or Cheerio.