Greedy vs lazy quantifiers: .* vs .*?
.* is greedy: it takes as much as it can, then backs off. .*? is lazy: it takes as little as it can, then extends. On say "hi" and "bye", "(.*)" captures hi" and "bye, while "(.*?)" captures hi and then bye.
JavaScript ✓PCRE ✓
No catastrophic-backtracking patterns found. (A heuristic check: nested and adjacent repeats, overlapping alternatives.)
\"The character '"' literallyliteral(…)Capturing group #1: remembers what it matchedgroup.Any character except a line breakcharacter type*?Repeat it zero or more times, as few as possible (lazy)quantifier\"The character '"' literallyliteral
1 capturing group.
Examples
Checked at build time in JavaScript and in Python's re.
| Input | Matches? |
|---|---|
say "hi" and "bye" | ✓ match |
"" | ✓ match |
no quotes here | ✗ no match |
"unterminated | ✗ no match |
he said "line one
line two" | ✗ no match |
'single quotes' | ✗ no match |
Try your own text
One string per line: each line is tested on its own (the m flag is added here).
How it works
Both versions find the same starting quote. The difference is where they stop. Greedy .* first runs to the end of the line, then backtracks one character at a time until it finds a ". That is the last quote on the line. Lazy .*? tries zero characters first, then one more each time the next " fails to match, so it stops at the first closing quote.
A negated class is usually the better tool. "([^"]*)" cannot run past a quote at all, so there is nothing to backtrack, and it states the intent directly: the content is everything that is not a quote. Lazy quantifiers are the right choice when the end marker is longer than one character, such as <!--[\s\S]*?-->.
Variants
/\"(.*)\"/gGreedy: from the first quote to the last quote on the line./\"([^\"]*)\"/gNegated class: no backtracking, and it matches across line breaks.
Gotchas
.does not match line breaks, so a quoted string that spans lines never matches. Use thesflag or[^"]*, which does cross lines.- Neither version understands escapes. In
"a \"quoted\" word", the lazy pattern stops at the escaped quote. Use"((?:[^"\\]|\\.)*)"for strings with backslash escapes. - Lazy does not mean fast. On a string with no closing quote,
.*?still tries every length before failing. - Lazy quantifiers change where a match ends, not where it starts. The engine still begins at the leftmost possible position.
Related explainers
- Regex to match or strip HTML tagsWhat <[^>]*> does to HTML, where it breaks (comments, > in attributes, scripts), and why stripping tags...
- Catastrophic backtracking, explained with ^(a+)+$Why ^(a+)+$ takes exponential time on input like aaaa...b, how nested quantifiers cause ReDoS, and the...
- Regex to find repeated words ("the the")Find doubled words like "the the" with a backreference, why word boundaries matter, and the cases where...