> ## Content Index
> Fetch the complete content index at: https://debugly.dev/llms.txt
> Use this file to discover other available public pages before exploring further.

# The Greedy Regex That Swallowed Three Fields at Once
- URL: https://debugly.dev/greedy-regex-swallowed-fields/
- Published: 2026-10-09T15:28:00.000Z
- Updated: 2026-10-17T05:56:05.000Z
- Description: The pattern matched as much as it could instead of as little as it needed, so one capture group ate the delimiter and the two fields after it, and the parse…
- Author: Rohit Bhadani
- Tags: Bug Hunt, Parsing, Python

Between the rows and the two missing fields sit 6 quiet assumptions, and the incident is the story of one of them failing.

Strip the greedy quantifier down and one property makes it silent: the match succeeds, so the parse reports no error, and the success is the wrong capture, and the wrong capture is the data, and the data is the incident.

This was a log import in Python, and the mechanics are the same in any regex engine where quantifiers are greedy by default.

## The hypotheses that failed

The first hypothesis was the file, and the file was checked, and the file was the correct format, and the correct ruled out the file. The second hypothesis was the encoding, and the encoding was checked, and the encoding was UTF-8, and the UTF-8 ruled out the encoding. The third hypothesis was the delimiter, and the delimiter was the pipe, and the pipe was present in every line, and the present ruled out the delimiter.

The dead end was the clue, because the file, the encoding and the delimiter were all correct, and the correct with the wrong parse is the pattern, and the pattern was the suspect, and the suspect was the capture, and the capture was the first group, and the group was the whole line, and the whole was the greedy, and the greedy was the dot star.

The specific finding was the capture group's content, and the content was the entire line, and the entire was the three fields, and the three was the swallow, and the swallow was the pattern's behaviour, and the behaviour was the greedy, and the greedy was the default, and the default was the assumption, and the assumption was the bug.

## The breakthrough

The breakthrough was printing the groups, and the printed was the first group, and the first group was the whole line, and the whole was the dot star, and the dot star was the match as much as possible, and the possible was the rest of the line, and the rest was the delimiter included, and the included was the second and third fields, and the fields were the swallowed, and the swallowed was the greedy's definition.

The pattern was written as dot star then pipe then capture, and the dot star consumed the pipes, and the consumed was the last pipe, and the last was the only one left, and the left was the capture, and the capture was the final field, and the final was the third, and the third was the one field instead of the three, and the instead was the incident.

The fix was the lazy quantifier, and the lazy was the question mark, and the question mark was the match as little as possible, and the little was the first pipe, and the first was the correct split, and the correct was the three fields, and the three was the parse, and the parse was the fix, and the fix was the one character.

This is the same family as [the model-written regex that backtracked for nine minutes](https://debugly.dev/ai-regex-catastrophic-backtracking/), and the shared property is the one worth fixing.

## What I changed

**Made the quantifiers lazy.** The dot star became the dot star question mark, and the question mark was the lazy, and the lazy was the minimal match, and the minimal was the first delimiter, and the delimiter was the split, and the split was the three fields, and the fields were the fix.

**Replaced the dot with a negated class.** The dot star became the not-pipe star, and the not-pipe was the cannot-cross, and the cannot-cross was the field's boundary, and the boundary was the fix, and the fix was the explicit, and the explicit was the safer than the lazy, because the class cannot match the delimiter at all.

**Used split instead of match.** The parse became the split on the pipe with a limit, and the split was the field separation, and the separation was the intent, and the intent was the simpler code, and the simpler was the fix, and the fix was the tool that matched the job, because the regex was doing the split's work.

**Asserted the field count.** The parse checks the number of fields, and the check is the guard, and the guard is the failure on the wrong count, and the failure is the loud, and the loud is the fix, and the fix is the assertion, and the assertion is the regression guard, because the wrong count was the silent bug.

**Added a test with the delimiter inside a field.** The test uses a quoted field containing the pipe, and the containing is the edge case, and the edge case is the parse's real difficulty, and the difficulty is the reason the split needs the quote handling, and the handling is the fix.

## The takeaway

A greedy quantifier matches as much as it can while still allowing the overall pattern to succeed, so dot star before a delimiter consumes every delimiter except the last. Use lazy quantifiers or negated character classes, prefer split for field separation, assert the field count, and test a delimiter inside a quoted field.

The import ran clean and every row was wrong, because the pattern's first capture group ate the whole line and the last pipe was the only one left to split on. The lesson I keep is the property, not the incident.

---

Published on [debugly.dev](https://debugly.dev/) by [Rohit Bhadani](https://debugly.dev/about/) | Founder of [Krova Cloud](https://krova.cloud/?ref=debugly.dev)