The single most common regex bug is a greedy .* swallowing far more text than intended.
Greedy quantifiers grab as much as they can, then give back characters one at a time until the rest of the pattern matches. With <.+> on <b>bold</b>, .+ first runs to the end of the string, then backs off until it finds the last >, so the match is the whole <b>bold</b>.
Adding ? makes a quantifier lazy (reluctant): *?, +?, ??, {m,n}?. A lazy quantifier takes as little as possible and expands only when the rest of the pattern fails. <.+?> matches <b> and then </b>.
Often an even better fix is a negated character class that can't cross the delimiter: <[^>]+>. It says exactly what you mean ("everything up to the next >"), it never backtracks across delimiters, and it behaves the same in engines without lazy quantifiers (POSIX grep and sed).
Laziness changes which match is found, not where the search starts: matches are still found leftmost first. a.*?b on xaab matches aab, starting at the first a.
Remember that . doesn't match newlines by default, so .* stops at the end of a line; that's a frequent reason a multi-line extraction "doesn't work".
Example
import re
html = "<b>bold</b> and <i>italic</i>"
print(re.findall(r"<.+>", html)) # ['<b>bold</b> and <i>italic</i>']
print(re.findall(r"<.+?>", html)) # ['<b>', '</b>', '<i>', '</i>']
print(re.findall(r"<[^>]+>", html)) # ['<b>', '</b>', '<i>', '</i>']
s = 'name="Ana" city="Rome"'
print(re.findall(r'"(.*)"', s)) # ['Ana" city="Rome']
print(re.findall(r'"(.*?)"', s)) # ['Ana', 'Rome']
print(re.findall(r'"([^"]*)"', s)) # ['Ana', 'Rome']
print(re.search(r"a.*?b", "xaab").group()) # aab (leftmost start wins)Remember
- Greedy quantifiers take the most, then give back; lazy ones take the least
- Add ? to make a quantifier lazy: *? +? ?? {m,n}?
- A negated class like [^"]* is often clearer and faster than .*?
Watch out for
- Using ".*" to match a quoted string on a line that contains several quoted strings.