Greedy versus lazy quantifiers

Regular expressions · Practical · 10 min

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.

Check yourself

  1. What does re.findall(r'\((.*)\)', 'f(a) g(b)') return?
  2. Which pattern extracts the contents of each pair of square brackets most robustly?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/re.html#regular-expression-syntax