Regex Quantifiers — *, +, ?, {n}, and Greedy vs Lazy Matching
Regex quantifiers control how many times a pattern matches: * (zero or more), + (one or more), ? (zero or one), {n,m} (specific range). Here's how each quantifier works and...
Quantifiers specify how many times a pattern element must match. Misusing them is the most common source of regex bugs — especially the difference between greedy and lazy matching.
Use the Regex Tester to test quantifier patterns on real text.
The quantifiers
| Quantifier | Meaning | Matches |
|---|---|---|
* | Zero or more | “ or a or aaa |
+ | One or more | a or aaa (not empty) |
? | Zero or one | “ or a |
{n} | Exactly n | aaa for {3} |
{n,} | n or more | aaa, aaaa, etc. |
{n,m} | Between n and m | aa, aaa for {2,3} |
* (zero or more)
/ab*c/.test('ac') // true (zero b's)
/ab*c/.test('abc') // true (one b)
/ab*c/.test('abbbc') // true (three b's)
/ab*c/.test('adc') // false (d not b)
// Common pattern — match anything:
/a.*z/ // 'a' followed by anything, ending in 'z'
'abcdefz'.match(/a.*z/) // ['abcdefz']
+ (one or more)
/\d+/.test('123') // true
/\d+/.test('') // false
/\d+/.test('abc') // false
// Extract numbers from text:
'I have 3 cats and 12 dogs'.match(/\d+/g) // ['3', '12']
// Difference from *:
/\d*/.test('') // true (zero digits)
/\d+/.test('') // false (needs at least one)
? (zero or one — optional)
// Make a character optional:
/colou?r/.test('color') // true (u optional)
/colou?r/.test('colour') // true
// Optional group:
/https?:\/\//.test('http://example.com') // true
/https?:\/\//.test('https://example.com') // true
// Phone number with optional country code:
/(\+1)?[ -]?\d{3}[ -]?\d{3}[ -]?\d{4}/
{n,m} (range)
// Exactly 5 digits:
/^\d{5}$/.test('12345') // true
/^\d{5}$/.test('1234') // false (only 4)
/^\d{5}$/.test('123456') // false (6 digits)
// 2 to 4 characters:
/^[a-z]{2,4}$/.test('ab') // true
/^[a-z]{2,4}$/.test('abcd') // true
/^[a-z]{2,4}$/.test('abcde') // false
// US zip code (5 digits, optional 4-digit extension):
/^\d{5}(-\d{4})?$/
Greedy vs lazy matching
By default, quantifiers are greedy — they match as much as possible while still allowing the overall pattern to succeed.
const html = '<b>bold</b> and <b>also bold</b>';
// Greedy (default):
html.match(/<b>.*<\/b>/)
// ['<b>bold</b> and <b>also bold</b>']
// Matches from first <b> to LAST </b>
// Lazy (add ?):
html.match(/<b>.*?<\/b>/)
// ['<b>bold</b>']
// Matches shortest possible string
Add ? after any quantifier to make it lazy:
| Greedy | Lazy | Meaning |
|---|---|---|
* | *? | Zero or more, lazy |
+ | +? | One or more, lazy |
? | ?? | Zero or one, lazy |
{n,m} | {n,m}? | Range, lazy |
Practical lazy matching examples
// Extract content inside HTML tags:
'<title>My Page</title>'.match(/<title>(.*?)<\/title>/)[1]
// 'My Page'
// Extract all items in quotes:
'"apple" and "banana"'.match(/"[^"]*"/g)
// ['"apple"', '"banana"']
// (using negated character class is better than .*? for quotes)
// JSON string value (handles escaped quotes):
/"(?:[^"\\]|\\.)*"/.exec('"hello \\"world\\""')[0]
// '"hello \"world\""'
Possessive quantifiers (atomic groups)
Some regex engines support possessive quantifiers (*+, ++, ?+) that don’t backtrack:
// JavaScript doesn't have possessive quantifiers directly
// Use atomic groups via a workaround or different engine
// Python (3.11+) supports possessive:
// \d++ — match digits without backtracking
Catastrophic backtracking
Nested quantifiers on overlapping patterns can cause exponential backtracking:
// DANGEROUS — catastrophic backtracking:
/^(a+)+$/.test('aaaaaaaaaaaaaaaaaaaaaaaaaaab')
// This can take seconds or minutes!
// WHY: (a+)+ tries every way to split the a's among groups
// then fails the final b check, backtracks, tries again...
// SAFER: be more specific
/^a+$/.test('aaaaaaaaab') // Immediately fails
Always test regexes with input that should fail — that’s when catastrophic backtracking strikes.
Related tools
- Regex Tester — test regex patterns online
- Regex Lookahead and Lookbehind — zero-width assertions
- Regex Patterns Cheatsheet — pattern reference
Related posts
- The 2026 Regex Cheatsheet (PCRE, JS, Python — Side by Side) — A dense reference for modern regex: anchors, character classes, quantifiers, loo…
- Catastrophic Backtracking: The Regex That Kills Your Server — One regex with nested quantifiers can reduce your server to 100% CPU in millisec…
- JavaScript Regex Flags — g, i, m, s, u, and v Explained — JavaScript regex flags change how patterns match. Learn when to use global /g, c…
- Regex Lookahead and Lookbehind — Zero-Width Assertions Explained — Regex lookaheads and lookbehinds match positions without consuming characters. H…
- Regex Patterns — Ready-to-Use Patterns for Email, Phone, URL, and More — Ready-to-use regular expression patterns for validating emails, phone numbers, U…
Related tool
Test regular expressions with live match highlighting and explanation.
Written by Mian Ali Khalid. Part of the Dev Productivity pillar.