skip to content

questions

5

In a regular expression, what is the difference between a greedy quantifier and a lazy one?

level: juniorimportance: must knowfreq 70%

answer

  1. same language, different preferred match
  2. how much does the repeat take first?
  3. greedy takes all, then gives back
  4. lazy starts empty, grows by one
  5. a negated class avoids the question

basics

~20 s

A greedy quantifier takes as many repetitions as it can and gives characters back only when the rest of the pattern fails; a lazy one takes as few as possible and grows one at a time.

solid answer

~40 s

Both forms accept exactly the same strings; they differ in which match is preferred, and therefore in what you read out afterwards. `.*` first consumes as far as it can, then releases one character at a time until the remainder of the pattern can succeed, so it settles on the longest run that still works. `.*?` starts at zero repetitions and extends one at a time, so it settles on the shortest run that works. On the line `user=alice;action=login;` the pattern `=(.*);` captures `alice;action=login`, because the greedy run gives back only as far as the *last* `;`. Written lazily, `=(.*?);` captures `alice`. The preference is local to that quantifier — it is not a promise about the length of the overall match.

go deeper

for a junior

Remember the direction of each: greedy grabs the most it can and backs off, lazy grabs the least and grows. Be able to say which one stops at the first delimiter in a key=value line.

for a middle

Explain that both forms accept the same language and only the preference order differs, and show the backtracking sequence that leaves the longest run in the capture group.

for a senior

Show the habit of reading every unrestricted run in a pattern and asking what it can swallow on a malformed line, then replacing it with a negated class so the boundary is stated rather than inferred.

for a principal

Frame it as a contract question: patterns that rely on a preference order to stop in the right place are fragile when input shape drifts, so a house style that requires explicit delimiters pays for itself across a fleet of extractors.

## The two forms describe the same language A **quantifier** says how many times the element before it may repeat: `*` is zero or more, `+` is one or more, `{2,5}` is a bounded repeat. Writing `?` after a quantifier makes it **lazy** (also called reluctant or non-greedy): `*?`, `+?`, `{2,5}?`. The first thing to pin down is what does *not* change. A greedy pattern and its lazy twin accept exactly the same set of strings — as formal languages they are identical, and a machine built from either recognises the same inputs. What changes is which of the many valid matches is **preferred**, and therefore what ends up in the capture groups. That matters because a pattern applied to real input almost always has several valid matches and you only ever see one of them. ## Greedy: take everything, then give it back A greedy quantifier is not a promise to swallow the rest of the input; it is an **order of attempts**. Applying `=(.*);` to `user=alice;action=login;`: 1. The match begins at the leftmost position where any match can start — here the first `=`, at offset 4. 2. `.*` takes everything to the end of the line. 3. The pattern still needs a `;`, and there is nothing left, so `.*` gives one character back. 4. It keeps giving characters back until a `;` sits at the current position — which happens at the **final** `;`. 5. The whole match is `=alice;action=login;` and group 1 holds `alice;action=login`. The field extractor was meant to pull one field and it swallowed two. Nothing malfunctioned: the pattern asked for the longest run of any character that still leaves a `;` behind, and that is what it got. ## Lazy: take nothing, then grow A lazy quantifier reverses the order. `=(.*?);` starts `.*?` at zero characters, checks whether a `;` follows, and only if that fails does it take one more character. On the same line it stops the moment the first `;` appears, so group 1 holds `alice`. Again nothing about the *possible* matches changed — only which one is tried first. ## The same line, three patterns | pattern | whole match on `user=alice;action=login;` | group 1 | |---|---|---| | `=(.*);` | `=alice;action=login;` | `alice;action=login` | | `=(.*?);` | `=alice;` | `alice` | | `=([^;]*);` | `=alice;` | `alice` | The third row is the one experienced authors reach for. A **negated character class** — "any character that is not a delimiter" — states the intent directly instead of relying on a preference order to stop in the right place, and it reads the same to every engine. ## Choosing between them - Use **lazy** when the closing delimiter is the *first* one after the field: quoted values, tag-like wrappers, a key up to the first separator. - Use **greedy** when you genuinely want the widest span: everything up to the last separator, a trailing remainder, the final extension in a name. - Prefer a **negated class** whenever the boundary is a single known character; it removes the question entirely. - Do not assume lazy is cheaper. If the text you are looking for sits far to the right, a lazy quantifier extends one character at a time all the way there, while a greedy one may land near it immediately. Which is cheaper depends on the input, not on the marker. ## What neither form changes - **The set of strings the pattern accepts.** Only the preferred match changes. - **Where the match starts.** The starting position is chosen first — the leftmost position at which any match exists — and the quantifier's preference only decides among the matches available there. - **The meaning of the surrounding pattern.** Anchors, classes and group numbering behave identically either way. - **Whether the overall match is the longest one possible.** Greedy expresses a preference at one quantifier; an earlier part of the pattern can still settle on a shorter overall span. The practical habit is to read every `.*` in a pattern and ask "what is it allowed to swallow on a bad line?". A field extractor that looks correct on one well-formed sample and returns too much on the next one almost always has a greedy run crossing a delimiter it was never meant to cross.

  • Does making a quantifier lazy change which strings the pattern can match at all?
    No. The two forms accept the same language; an input either has a match or it does not, regardless of the marker. What changes is which of the valid matches is reported, and therefore the spans and captures you read out afterwards.
  • How would you rewrite `=(.*?);` without using a lazy quantifier?
    Use a negated character class: `=([^;]*);`. It says directly that the field contains no delimiter, so there is only one match to find rather than a preference order that happens to stop in the right place, and it reads identically under every match-semantics convention.

Greedy is a shopper who fills the bag to the top and then puts items back until it closes; lazy is one who puts in a single item and adds another only when told it is not enough.

saying these in an interview costs you the question

  • Says greedy and lazy versions accept different sets of strings
  • Thinks a lazy quantifier is always faster than a greedy one
  • Believes a lazy quantifier can never span a long piece of text
  • Treats greedy as a guarantee of the longest overall match
  • Puts an unrestricted run between two delimiters and blames the engine
open as a page

Why does an unanchored regular expression accept input that its author meant to reject?

level: middleimportance: must knowfreq 58%

basics

~20 s

Searching asks whether a match exists anywhere in the subject, not whether the whole subject matches. Without a start and end anchor, a pattern for four digits happily matches the four digits buried inside a longer string.

open as a page

In a regular expression with nested capture groups, how are the group numbers assigned?

level: middleimportance: should knowfreq 48%

basics

~20 s

Groups are numbered by the left-to-right order of their opening parentheses, so an outer group always has a lower number than the groups nested inside it. Group 0 is the whole match, and non-capturing groups take no number.

open as a page

Why can one regular-expression engine return a shorter match than another for the same alternation?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Two different match-selection rules are in play. Leftmost-first returns the first match the pattern's own preference order reaches, so branch order decides; POSIX leftmost-longest returns the longest match available at that position, whatever the order.

open as a page

Why can a pattern that is able to match the empty string stall a global find-and-replace?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

A global scan continues from the end of the previous match. A zero-width match ends where it began, so the next attempt starts at the same position and succeeds again, forever — unless the engine forces the position forward by one.

open as a page