Skip to content
DevMeme
2380 of 7435
The Regex Quantifier Betrayal
Bugs Post #2646, on Jan 21, 2021 in TG

The Regex Quantifier Betrayal

Why is this Bugs meme funny?

Level 1: Tiny Door, Big Mess

Imagine you ask a guard to let in anyone carrying at least one ticket, but you accidentally say, "Let in people carrying zero or more tickets." Now people with no tickets walk inside, and the ticket checker later gets blamed for being confused. The meme is funny because one tiny symbol made the computer follow the wrong rule perfectly.

Level 2: Star Versus Plus

A regular expression is a compact pattern for searching text. In this meme, the pattern is trying to find something like an index value that contains two numbers separated by an underscore. The visible code uses (\d*) twice. \d means "digit," parentheses mean "capture this part," and * means "zero or more."

That last part is the problem. If the input contains index":"_42" or index":"12_", the pattern can still match because one side is allowed to have no digits at all. Later, when Python tries to run int(...) on the captured value, an empty string causes an error. This is a common early Debugging lesson: the line that crashes is not always the line that introduced the bug.

Using + would say "one or more digits," which is usually what the programmer meant:

r'index":"(\d+)_(\d+)"'

The meme caption says, "The Look your regex gives you when you use asterisks instead of plus," because experienced programmers know that regex syntax can be brutally precise. It does not infer your intent. It reads *, accepts emptiness, and then watches the rest of the program deal with the consequences.

Level 3: Quantifier Betrayal

The entire joke is hiding in one visible character:

re.findall('index":"(\d*)_(\d*)"',

That * after \d means "match zero or more digits." In other words, the pattern is perfectly happy with an empty capture group. The code just below it appears to feed those groups into int(...), which is where the trap snaps shut: the regex politely accepts bad or incomplete text, then the parser later has to explain why int("") is not a number. Classic debugging theater. The bug was admitted at the border, got a badge, and only caused trouble once it reached the database-looking part of the building.

This is why RegularExpressions are such fertile ground for SubtleBugs. A regex is often used as both a search tool and an input validator, but those are not the same job. findall asks, "Can I find something that satisfies this pattern?" If the pattern says empty digits are acceptable, then findall is not betraying you; it is obeying you with maliciously literal professionalism.

The asterisk-versus-plus difference is small enough to vanish in a dark editor theme but large enough to change the contract:

Pattern Meaning Consequence
\d* zero or more digits accepts "", "7", "123"
\d+ one or more digits rejects "", accepts "7", "123"

The senior-developer pain here is not that someone made a typo. It is that this typo can survive code review because the line "looks regex-shaped." The string has escaped quotes, capture groups, underscores, and syntax highlighting doing its little nightclub routine. Everyone's eyes slide over the quantifier because the code appears plausibly intentional. Then a weird edge case arrives from production data, an empty group sneaks through, and the failure appears downstream in numeric conversion rather than at the pattern boundary where the real mistake lives.

The better fix is not just "replace * with + and call it a day," although that is probably the immediate patch. The robust fix is to treat the regex as part of InputValidation:

match = re.search(r'index":"(\d+)_(\d+)"', text)
if match is None:
    raise ValueError("missing numeric index pair")

x, y = map(int, match.groups())

That version makes the data contract explicit: there must be two numeric fields, and conversion only happens after the match proves they exist. It also keeps the bug closer to its cause, which is roughly half of debugging: finding where reality first stopped matching the story the code was telling.

Description

A very tight dark-theme code screenshot shows syntax-highlighted Python-like regex code, including a visible fragment resembling `re.findall('index":"(\d*)_(\d*)"', ...)` and a following line converting captured groups with `int(lGroup[0][0])` and another `int(...)` call. The key visible pattern uses `\d*`, which means zero or more digits, rather than `\d+`, which would require at least one digit. The sibling caption says, "The Look your regex gives you when you use asterisks instead of plus," framing the crop as a classic regular-expression footgun. The technical humor is that one quantifier character can silently admit empty captures and push the failure downstream into parsing or type conversion.

Comments

18
Anonymous ★ Top Pick The gap between `\d*` and `\d+` is exactly one character wide and somehow still large enough to fit an outage.
  1. Anonymous ★ Top Pick

    The gap between `\d*` and `\d+` is exactly one character wide and somehow still large enough to fit an outage.

  2. Deleted Account 5y

    parsing json with regex why

    1. Deleted Account 5y

      Лан

    2. @cozgerest 5y

      Лан

    3. @goistheworstlanguageever 5y

      Лан

    4. @LionElJonson 5y

      Лан

    5. @gas2600 5y

      coz can и лан

    6. @foreshape 5y

      Лан

    7. @akionka 5y

      Лан

    8. Deleted Account 5y

      Лан

    9. Deleted Account 5y

      Лан

    10. @nenten 5y

      Лан

    11. Deleted Account 5y

      Лан

  3. Deleted Account 5y

    validating is not parsing

  4. Deleted Account 5y

    regex in the pic won't match "index":\n"245_445" which is a totally valid json

    1. @ogroleg 5y

      👍

  5. @willowfragment 5y

    "asterisks"... "plus"... why not call them what they are, which is "zero or more occurrences " and "one or more occurrences "

  6. @s2504s 5y

    B/c this is easy?

Use J and K for navigation