Regular expressions are one of the most powerful and, at the same time, intimidating tools in a developer’s arsenal. A string like (?<=\d{3})\s(?=\w+) can bewilder even an experienced programmer. However, behind this apparent chaos of symbols is a coherent system that is entirely possible to master. In this guide, we’ll cover regular expressions, from the simplest constructs to advanced techniques, with plenty of practical examples.
What are regular expressions?
Regular expressions (regular expressions, regex, regexp) are a formal language for describing text patterns. They let you find, extract, and replace parts of text according to specified rules. Regex is used in virtually all programming languages, text editors, command-line utilities, and server configurations.
Historically, regular expressions originated in the work of mathematician Stephen Kleene in the 1950s. Ken Thompson brought them into programming by adding regexp support to the Unix text editor ed. Since then, regex has become an integral part of text processing.
Typical tasks solved with regular expressions include:
- Data validation — checking email addresses, phone numbers, passwords, and URLs
- Search and extraction — finding specific patterns in text
- Text replacement — bulk renaming, formatting, and cleaning data
- Parsing — processing logs, configuration files, and CSV
- Routing — redirect rules in htaccess and Nginx
Basic syntax
Literals
The simplest type of regular expression consists of ordinary characters that match themselves. The pattern cat finds the substring “cat” in the words “cat,” “concatenate,” and “catalog.” By default, regular expressions are case-sensitive.
Metacharacters
Metacharacters are symbols with special meanings that form the basis of the regex language:
| Metacharacter | Meaning |
|---|---|
. | Any character (except a newline) |
^ | Start of a line |
$ | End of a line |
* | 0 or more repetitions |
+ | 1 or more repetitions |
? | 0 or 1 repetition |
| | Logical OR (alternation) |
() | Grouping |
[] | Character class |
{} | Quantifier specifying the number of repetitions |
\ | Escaping |
Escaping
To find a metacharacter as an ordinary character, precede it with a backslash \. For example, \. searches for a literal period, not “any character”:
index\.html — совпадёт с «index.html», но не с «indexXhtml»These are escaped: \. \* \+ \? \\ \( \) \[ \{ \| \^ \$
Character classes
Custom classes [abc]
Square brackets define a set of characters for a single position:
[aeiou] — любая гласная (строчная)
[a-z] — любая строчная латинская буква
[A-Z] — любая заглавная латинская буква
[0-9] — любая цифра
[a-zA-Z0-9] — буква или цифра
[a-f0-9] — шестнадцатеричная цифраNegation uses the ^ character at the beginning:
[^0-9] — любой символ, кроме цифры
[^a-z] — любой символ, кроме строчной латинской буквыInside a character class, most metacharacters lose their special meaning. Exceptions: ], \, ^ (when first), - (range). To include a hyphen as a literal: [-abc] or [abc-].
Predefined classes
| Class | Equivalent | Description |
|---|---|---|
\d | [0-9] | Digit |
\D | [^0-9] | Non-digit |
\w | [a-zA-Z0-9_] | Letter, digit, or underscore |
\W | [^a-zA-Z0-9_] | Non-word character |
\s | [\t\n\r\f\v ] | Whitespace character |
\S | [^\t\n\r\f\v ] | Non-whitespace character |
Examples:
\d{3}-\d{2}-\d{2} — телефон вида 123-45-67
\w+@\w+\.\w+ — упрощённый email
\s+ — один или более пробельных символовDot (.)
Matches any character except \n (behavior changes with the s flag):
a.b — совпадёт с aXb, a1b, a.b; не совпадёт с ab, a\nb
.+ — один или более любых символовQuantifiers
Quantifiers specify how many times the preceding element is repeated.
Basic quantifiers
| Quantifier | Meaning | Example |
|---|---|---|
* | 0 or more | go*gle → ggle, gogle, google... |
+ | 1 or more | go+gle → gogle, google... |
? | 0 or 1 | colou?r → color, colour |
{n} | Exactly n | \d{4} → exactly 4 digits |
{n,} | n or more | [a-z]{3,} → 3+ lowercase letters |
{n,m} | From n to m | \d{2,4} → from 2 to 4 digits |
Greedy vs. lazy
By default, quantifiers are greedy—they match as much as possible. Adding ? makes them lazy—they match as little as possible:
Текст: <b>жирный</b> и <b>ещё жирный</b>
Жадный: <b>.+</b> → <b>жирный</b> и <b>ещё жирный</b> (одно совпадение)
Ленивый: <b>.+?</b> → <b>жирный</b> и <b>ещё жирный</b> (два совпадения)| Greedy | Lazy | Meaning |
|---|---|---|
* | *? | 0 or more (minimum) |
+ | +? | 1 or more (minimum) |
? | ?? | 0 or 1 (prefers 0) |
{n,m} | {n,m}? | From n to m (prefers n) |
PCRE (PHP, Java) also supports possessive quantifiers *+, ++, ?+—they work like greedy quantifiers but do not backtrack. Useful for optimization.
Anchors and boundaries
Anchors indicate a position in a string without matching characters.
Start and end of a line: ^ and $
^Hello — строка начинается с «Hello»
world$ — строка заканчивается на «world»
^\d{4}-\d{2}-\d{2}$ — строка целиком является датойWith the m flag, the ^ and $ anchors apply to each line in multiline text.
Word boundary: \b
\b is the position between a “word” character (\w) and a “non-word” character (\W):
\bcat\b — слово «cat» целиком
совпадёт: «the cat sat»
не совпадёт: «concatenate», «catalog»
\bgo\w* — слова на «go»: go, gone, google\B is a position inside a word (the opposite of \b).
Python and PHP also provide \A and \Z—the beginning and end of the entire text, ignoring the m flag.
Groups and backreferences
Capturing groups
Parentheses group elements and save the matched text:
(ab)+ — «ab», «abab», «ababab»...
(cat|dog) — «cat» или «dog»
(\d{2})\.(\d{2})\.(\d{4}) — дата: группа 1 — день, 2 — месяц, 3 — годconst m = '07.03.2026'.match(/(\d{2})\.(\d{2})\.(\d{4})/);
// m[1] = '07', m[2] = '03', m[3] = '2026'Named groups
const m = '07.03.2026'.match(/(?<day>\d{2})\.(?<month>\d{2})\.(?<year>\d{4})/);
// m.groups.day = '07', m.groups.month = '03', m.groups.year = '2026'In Python, the syntax is different: (?P<name>...):
m = re.match(r'(?P<day>\d{2})\.(?P<month>\d{2})\.(?P<year>\d{4})', '07.03.2026')
m.group('year') # '2026'Non-capturing groups (?:...)
Grouping without capturing does not affect numbering and is faster:
(?:ab)+ — повторение «ab», группа не сохраняется
(?:http|https):// — протокол без захвата
(?:\d{1,3}\.){3}\d{1,3} — упрощённый IPv4Backreferences
\1, \2 refer to the text captured by the corresponding group:
(\w+)\s+\1 — повторяющееся слово: «the the», «is is»
(['"]).*?\1 — строка в одинаковых кавычках
<(\w+)>.*?</\1> — парный HTML-тег// Нахождение дублей
'the the quick fox fox'.match(/\b(\w+)\s+\1\b/g);
// Замена с группами
'Иванов Иван'.replace(/(\S+)\s(\S+)/, '$2 $1'); // 'Иван Иванов'Lookahead and lookbehind
Lookaround assertions specify a condition ahead of or behind the current position, without including the checked text in the result.
Positive lookahead (?=...)
\d+(?=\s?руб) — число перед «руб»: «500 руб.» → 500
\w+(?=\() — слово перед скобкой: «func(x)» → funcNegative lookahead (?!...)
\d+(?!\s?руб) — число НЕ перед «руб»: «500 долл.» → 500
^(?!.*admin).*$ — строки без «admin»Positive lookbehind (?<=...)
(?<=\$)\d+ — число после $: «$100» → 100
(?<=class=")\w+ — значение после class=": «class="header"» → headerNegative lookbehind (?<!...)
(?<!\$)\d+ — число без $ перед ним: «€100» → 100
(?<!un)happy — «happy» без «un»: happy → да, unhappy → нетCombining them
(?<=<b>).*?(?=</b>) — содержимое между <b> и </b>
# Валидация пароля через множественные lookahead
^(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$Lookbehind is available in JavaScript starting with ES2018. In Python, it requires a fixed-length pattern. PCRE (PHP) partially removes this restriction.
Flags
Flags change how an expression behaves: /pattern/flags (JS) or a function parameter (Python, PHP).
| Flag | Name | Effect |
|---|---|---|
g | Global | All matches, not just the first |
i | Case-insensitive | Case-insensitive matching |
m | Multiline | ^/$ apply to each line |
s | DotAll | Dot matches \n |
u | Unicode | Full Unicode support |
x | Extended | Whitespace and comments in the pattern (Python, PHP) |
'banana'.match(/an/g); // ['an', 'an'] — все вхождения
/hello/i.test('HELLO'); // true
/^.$/u.test('😀'); // true — эмодзи как один символThe x flag lets you write readable patterns:
pattern = re.compile(r'''
^ # начало строки
(?P<protocol>https?) # протокол
:// # разделитель
(?P<domain>[\w.-]+) # домен
(?P<path>/\S*)? # путь (опционально)
$ # конец строки
''', re.VERBOSE)Summary of equivalent flags:
| Flag | JavaScript | Python | PHP |
|---|---|---|---|
g | /pattern/g | findall() / finditer() | preg_match_all() |
i | /pattern/i | re.IGNORECASE | /pattern/i |
m | /pattern/m | re.MULTILINE | /pattern/m |
s | /pattern/s | re.DOTALL | /pattern/s |
x | — | re.VERBOSE | /pattern/x |
Practical examples
You can test any of the patterns below in the Regex Tester on reChecker—the tool shows matches in real time and breaks down the structure of the expression.
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$| Part | Meaning |
|---|---|
[a-zA-Z0-9._%+-]+ | Local part (before @) |
[a-zA-Z0-9.-]+ | Domain |
\.[a-zA-Z]{2,} | Dot + TLD (at least 2 letters) |
Russian phone number
^(\+7|8)\s?\(?\d{3}\)?\s?\d{3}[-\s]?\d{2}[-\s]?\d{2}$Matches: +7 (999) 123-45-67, +79991234567, 8(999)1234567, 8 999 123 45 67.
URL
^https?:\/\/[\w.-]+\.[a-z]{2,}(\/\S*)?$Extended version with support for query parameters:
^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$IPv4 address
A naive \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} will accept 999.999.999.999. A correct pattern:
^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$Octet breakdown: 25[0-5] → 250–255, 2[0-4]\d → 200–249, [01]?\d\d? → 0–199.
Date (dd.mm.yyyy)
^(0[1-9]|[12]\d|3[01])\.(0[1-9]|1[0-2])\.(19|20)\d{2}$The pattern does not check validity (31 in February will match)—full validation requires program logic.
Password
At least 8 characters, with an uppercase letter, a lowercase letter, a digit, and a special character:
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=\[\]{}|;:,.<>?]).{8,}$Each (?=...) is a separate lookahead check, while .{8,} is the main length condition.
HTML tags
<([a-zA-Z][a-zA-Z0-9]*)\b[^>]*> — все открывающие теги
<title>(.*?)<\/title> — содержимое <title>
<[^>]+> — любой тег (для удаления)'<p>Текст <b>жирный</b></p>'.replace(/<[^>]+>/g, '');
// 'Текст жирный'Domain from a URL
'https://www.example.com/page'.match(/^https?:\/\/(?:www\.)?([^\/\s:]+)/);
// [1] = 'example.com'HEX color
^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$Duplicate words
\b(\w+)\s+\1\bFinds “and and,” “on on”—useful when proofreading text.
Numbers in text
-?\d+(?:[.,]\d+)?Matches: 42, -3.14, 1,5.
Replace multiple spaces with one
'слишком много пробелов'.replace(/\s{2,}/g, ' ');
// 'слишком много пробелов'Regex in different languages
JavaScript
const regex = /\d+/g;
const regex2 = new RegExp('\\d+', 'g');
regex.test('abc123'); // true
'abc123def456'.match(/\d+/g); // ['123', '456']
'abc123def456'.matchAll(/\d+/g); // итератор совпадений (ES2020)
'abc123'.search(/\d+/); // 3
'abc123'.replace(/\d+/, 'NUM'); // 'abcNUM'
'a1b2c3'.split(/\d/); // ['a', 'b', 'c', '']Features: no x flag; lookbehind and named groups are available starting with ES2018; matchAll is ES2020; the v flag is ES2024.
Python
import re
re.search(r'\d+', 'abc123') # Match (первое совпадение)
re.match(r'\d+', '123abc') # Match (только с начала строки)
re.fullmatch(r'\d+', '123') # Match (вся строка)
re.findall(r'\d+', 'a1 b2 c3') # ['1', '2', '3']
re.sub(r'\d+', 'N', 'a1 b2') # 'aN bN'
re.split(r'\s+', 'a b c') # ['a', 'b', 'c']
pattern = re.compile(r'(?P<year>\d{4})-(?P<month>\d{2})')
m = pattern.search('2026-03')
m.group('year') # '2026'Features: raw strings r'...', named groups (?P<name>...), re.match ≠ re.search, fixed-length lookbehind. The third-party module regex removes these limitations.
PHP
preg_match('/\d+/', 'abc123', $m); // $m[0] = '123'
preg_match_all('/\d+/', 'a1 b2 c3', $m); // $m[0] = ['1', '2', '3']
preg_replace('/\d+/', 'N', 'a1 b2 c3'); // 'aN bN cN'
preg_replace_callback('/\d+/', function ($m) {
return $m[0] * 2;
}, 'a1 b2 c3'); // 'a2 b4 c6'
preg_split('/[\s,]+/', 'one, two, three'); // ['one', 'two', 'three']Features: PCRE engine, delimiters /pattern/flags, recursion (?R), conditional patterns (?(cond)yes|no), and possessive quantifiers.
Differences between languages
| Feature | JavaScript | Python | PHP (PCRE) |
|---|---|---|---|
| Named groups | (?<name>...) | (?P<name>...) | Both forms |
| Variable-length lookbehind | No | No | Partial |
| Recursion | No | No | (?R), (?1) |
| Verbose mode | No | re.VERBOSE | x flag |
| Unicode by default | No (u flag) | Yes | No (u flag) |
| Atomic groups | No | No* | (?>...) |
| Possessive quantifiers | No | No* | *+, ++, ?+ |
* Available through the third-party module regex.
Optimization and performance
Catastrophic backtracking
The pattern (a+)+b on the string aaaaaaaaaaaa (without b) forces the engine to try an exponentially growing number of combinations. For 20 characters, that means more than a million attempts. Dangerous constructs:
(a+)+ — вложенные квантификаторы
(a|aa)+ — перекрывающиеся альтернативы
(.*a){10} — жадный .* с повторениемOptimization rules
1. Avoid nested quantifiers — (a+)+b → a+b
2. Narrow character classes:
<.*> — плохо, .* захватит слишком много
<[^>]*> — хорошо, отрицание конкретного символа3. Anchor patterns — ^ and $ let the engine rule out non-matches more quickly.
4. Compile and reuse:
pattern = re.compile(r'\d{3}-\d{2}-\d{2}')
for line in lines:
if pattern.search(line):
process(line)5. Use non-capturing groups — (?:...) is faster than (...) if capturing is not needed.
6. Factor out common prefixes:
(?:Monday|Tuesday|Wednesday) → (?:Mon|Tues|Wednes)day7. In PCRE, use possessive quantifiers — a++b instead of a+b prevents backtracking.
Use the Regex Tester on reChecker to test and debug patterns.
Reference table
Characters and character classes
| Symbol | Description |
|---|---|
. | Any character (except \n without the s flag) |
\d / \D | Digit / non-digit |
\w / \W | Word character / non-word character |
\s / \S | Whitespace / non-whitespace |
\t \n \r | Tab, line feed, carriage return |
Anchors
| Symbol | Description |
|---|---|
^ / $ | Start / end of line |
\b / \B | Word boundary / non-boundary |
\A / \Z | Start / end of text (Python, PHP) |
Quantifiers
| Greedy | Lazy | Description |
|---|---|---|
* | *? | 0 or more |
+ | +? | 1 or more |
? | ?? | 0 or 1 |
{n} | {n}? | Exactly n |
{n,} | {n,}? | n or more |
{n,m} | {n,m}? | From n to m |
Groups
| Syntax | Description |
|---|---|
(abc) | Capturing group |
(?:abc) | Non-capturing group |
(?<name>abc) | Named group (JS, PHP) |
(?P<name>abc) | Named group (Python, PHP) |
\1, $1 | Backreferences (in a pattern / in a replacement) |
Lookaround
| Syntax | Description |
|---|---|
(?=abc) | abc is ahead |
(?!abc) | abc is not ahead |
(?<=abc) | abc is behind |
(?<!abc) | abc is not behind |
Conclusion
Regular expressions are a skill that pays off many times over. A single line of regex can replace dozens of lines of procedural code. But balance matters: an expression that is too complex becomes unreadable and fragile. If a pattern takes more than two lines, consider breaking it down or using a parser.
Key principles:
- Start simple — build up the pattern gradually, testing at each step
- Test edge cases — empty strings, long strings, unexpected characters
- Use named groups — they make the pattern self-documenting
- Watch performance — avoid catastrophic backtracking
- Don’t parse HTML with regular expressions — DOM parsers are available for that
Regex is used in more than just application code. It underpins server configurations: rewrite rules in .htaccess and Nginx are built on regular expressions. Knowing how to write correct patterns directly helps with configuring routing, redirects, and access control.