Quantifiers
A quantifier says how many of the previous thing: "three digits," "one or more letters," "optional."
The big four
| Quantifier | Means | Example | Matches |
|---|---|---|---|
* |
Zero or more | a* |
`,a,aaa` |
+ |
One or more | a+ |
a, aaa (not empty) |
? |
Zero or one (optional) | colou?r |
color, colour |
{n,m} |
Between n and m | \d{2,4} |
12, 123, 1234 |
>>> re.findall(r"\d+", "abc123def45")
['123', '45'] # one or more digits, greedily
>>> re.findall(r"colou?r", "color colour")
['color', 'colour'] # the u is optional
>>> re.findall(r"\d{2,4}", "1 12 123 12345")
['12', '123', '1234'] # 2-4 digits; "12345" gives "1234"
All verified. {n} is exactly n; {n,} is n or more. The precise
forms ({4}, {2,4}) are your friends for fixed-format data like
dates and zip codes.
Greedy vs. lazy: the essential gotcha
By default, quantifiers are greedy — they grab as much as possible:
>>> re.search(r"<.*>", "<b>hi</b>").group()
'<b>hi</b>' # grabbed EVERYTHING between first < and last >
You wanted <b>, you got the whole tag pair. The fix: lazy
quantifiers (*?, +?) grab as little as possible:
>>> re.search(r"<.*?>", "<b>hi</b>").group()
'<b>' # stops at the first >
Both verified. The rule: when matching between delimiters (tags, quotes, brackets), reach for the lazy version first. Greedy is right when you want "everything up to the end" — lazy is right when you want "the smallest piece."
>>> re.findall(r'".*?"', 'say "hi" and "bye"')
['"hi"', '"bye"'] # lazy: two separate quoted strings
>>> re.findall(r'".*"', 'say "hi" and "bye"')
['"hi" and "bye"'] # greedy: one giant match
Verified. This pair of lines is worth staring at until it clicks — it's the single most common regex bug in the wild.
Common shapes
| Shape | Use |
|---|---|
\d+ |
A number |
\w+ |
A word / identifier |
".*?" |
A quoted string |
\d{4}-\d{2}-\d{2} |
An ISO date |
https?:// |
http:// or https:// |
\s* |
Optional whitespace (the polite glue between tokens) |
Next: Anchors