Skip to content
VLSI Mentor

Tcl Part I · Module 13 · Regular Expressions

Greedy and Non-Greedy Matching

You write a pattern to pull the first bracketed item out of a line, run it, and get back everything from the first bracket to the last one. The pattern is not wrong exactly — it is doing precisely what quantifiers do, which is take as much as they possibly can and then give back only as much as they must. Once you have seen that happen and understood the backtracking behind it, the two fixes are obvious and so is the third thing worth knowing: that greedy behaviour is sometimes exactly what you want, and switching it off by reflex causes its own bugs.

Intermediate11 min readTclPart IRegexGreedyQuantifiers

Part I · Module 13 · Chapter 13.8 · Greedy and Non-Greedy Matching

1. The surprise

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set s {[a] [b]}

regexp {\[(.*)\]} $s -> captured
puts "captured : '$captured'"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
captured : 'a] [b'

The intent was a. What came back is everything between the first [ and the last ].

2. Why: take everything, then give back the minimum

* and + are greedy. The engine does this:

  1. \[ matches the first [.
  2. .* takes the entire rest of the string — a] [b].
  3. The pattern still needs a ], and there is nothing left.
  4. So .* gives back one character at a time until a ] can match.
  5. The first position that works is the last ] in the string.

The result is a] [b — everything up to the final bracket.

Two parallel left-to-right flows matching bracketed text. A greedy dot-star runs to the end of the string and then backs up to the last closing bracket, capturing too much. A non-greedy dot-star-question stops at the first closing bracket and captures what was intended.Greedyas much as possibleRuns to the endthen backs upCaptures too muchthe last bracket winsNon-greedyas little as possibleStops at the firstclosing bracketCaptures one itemwhat you meant12
Figure 1 — what the two quantifiers do differently. A greedy quantifier consumes as much of the string as it can and then backtracks only far enough to let the rest of the pattern succeed, which lands on the last possible closing bracket. A non-greedy quantifier consumes as little as it can and extends only when forced, so it succeeds at the first closing bracket instead.

3. Fix one: the non-greedy quantifier

Adding ? after a quantifier makes it take as little as possible, extending only when forced:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set s {[a] [b]}

regexp {\[(.*?)\]} $s -> captured
puts "captured : '$captured'"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
captured : 'a'

Every quantifier has a non-greedy form:

GreedyNon-greedyMeans
**?zero or more, as few as possible
++?one or more, as few as possible
???zero or one, preferring zero
{n,m}{n,m}?n to m, as few as possible

The same fix on a different shape:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
puts "greedy     : '[regexp {<(.+)>} {<one><two>} -> m; set m]'"
puts "non-greedy : '[regexp {<(.+?)>} {<one><two>} -> m; set m]'"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
greedy     : 'one><two'
non-greedy : 'one'

4. Fix two: a negated character class

Often better, because it says what you mean rather than how much to take:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set s {[a] [b]}

regexp {\[([^\]]*)\]} $s -> captured
puts "captured : '$captured'"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
captured : 'a'

[^\]]* means "any number of characters that are not a closing bracket". It cannot overshoot, because the character that would end the match is excluded from what it can consume.

5. The third fix people reach for, and why not

The instinct when a pattern captures too much is to add more pattern:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set s {[a] [b]}

regexp {\[(.*)\] \[} $s -> captured
puts "captured : '$captured'"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
captured : 'a'

That works — and only because there happen to be exactly two bracketed items with a space between them. Give it one item and it fails entirely:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
puts "on one item : [regexp {\[(.*)\] \[} {[a]} -> m]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
on one item : 0

Whereas both real fixes still work:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
puts "non-greedy  : [regexp {\[(.*?)\]} {[a]} -> m]  -> '$m'"
puts "negated     : [regexp {\[([^\]]*)\]} {[a]} -> m]  -> '$m'"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
non-greedy  : 1  -> 'a'
negated     : 1  -> 'a'

6. Greedy is sometimes exactly right

Switching greediness off by reflex causes its own bugs. "Everything before the last slash" is a greedy question:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set path "a/b/c.txt"

puts "greedy     : '[regexp {^(.*)/} $path -> m; set m]'"
puts "non-greedy : '[regexp {^(.*?)/} $path -> m; set m]'"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
greedy     : 'a/b'
non-greedy : 'a'

For a directory name you want the greedy one; for the first path component you want the non-greedy one. Neither is the bug — they answer different questions.

You wantQuantifier
Up to the last occurrencegreedy .*
Up to the first occurrencenon-greedy .*? or a negated class

7. Greediness affects where the match ends, not whether it matches

A useful thing to know when debugging: changing greediness never turns a match into a non-match of the whole pattern:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set s "aaa"
puts "greedy   : [regexp {^a+$} $s]"
puts "non-greedy: [regexp {^a+?$} $s]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
greedy   : 1
non-greedy: 1

Both match, because the anchors force the whole string either way. Greediness only decides how much a quantifier takes when there is a choice — so if a pattern is not matching at all, greediness is not the cause and you should look at the classes and anchors instead.

8. A worked example

Pulling fields out of a bracketed log line, three ways:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set line {[2026-01-05] [ERROR] cannot open [input.csv] for reading}

puts "greedy, first group:"
regexp {\[(.*)\]} $line -> all
puts "  '$all'"

puts "non-greedy, all three:"
foreach {whole field} [regexp -all -inline {\[(.*?)\]} $line] {
    puts "  '$field'"
}

puts "negated class, all three:"
foreach {whole field} [regexp -all -inline {\[([^\]]*)\]} $line] {
    puts "  '$field'"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
greedy, first group:
  '2026-01-05] [ERROR] cannot open [input.csv'
non-greedy, all three:
  '2026-01-05'
  'ERROR'
  'input.csv'
negated class, all three:
  '2026-01-05'
  'ERROR'
  'input.csv'

The greedy version captured from the first bracket to the last, swallowing two fields and the text between them. The other two found all three fields correctly, and the negated class is the one to keep.

9. Exercises

Exercise 1. Why does {\[(.*)\]} on [a] [b] capture a] [b?

Exercise 2. Give two ways to make it capture a, and say which you prefer.

Exercise 3. Write the non-greedy forms of *, + and {2,5}.

Exercise 4. Why is adding \[ to the end of the pattern a poor fix?

Exercise 5. Give a case where greedy is the correct choice.

Exercise 6. A pattern matches nothing at all. Is greediness a likely cause?

Answers

1. Because * is greedy: it first takes the whole rest of the string, then gives back characters one at a time until the following \] can match. The first position that works is the last ], so the capture runs to there.

2. {\[(.*?)\]} with a non-greedy quantifier, or {\[([^\]]*)\]} with a negated character class. The negated class is preferable when there is an obvious terminator — it describes the content rather than the search strategy and cannot overshoot.

3. *?, +? and {2,5}?.

4. Because it bakes the surrounding context — "another bracket follows" — into the pattern, so it only works on input shaped like the example. A line with a single bracketed item fails to match at all.

5. Anything asking for the last occurrence: everything before the final slash in a path, the last dot in a filename, the outermost pair of brackets. Non-greedy would stop at the first.

6. No. Greediness decides how much a quantifier consumes when there is a choice, not whether the pattern can match. A total non-match points at the character classes, the anchors, or an escape that was eaten by quoting.

10. Remember

  • Quantifiers are greedy: they take as much as possible, then backtrack only as far as the rest of the pattern requires.
  • So .* between two delimiters runs to the last one, not the first.
  • ? after a quantifier makes it non-greedy — *?, +?, {n,m}?.
  • A negated character class is usually better — [^\]]* cannot overshoot and describes the content.
  • Do not fix greediness by adding surrounding context — that encodes your test data into the pattern.
  • Greedy is correct when you want the last occurrence, non-greedy when you want the first.
  • Greediness changes where a match ends, not whether it matches — a total non-match has a different cause.
  • For paths, use the file commands rather than either.

Where this fits

Part of the Tcl curriculum.