C.W.K.
Stream
Lesson 01 of 12 · published

Atomic Groups — (?>...)

~10 min · atomic, backtracking, performance

Level 0Pattern-Curious
0 XP0/90 lessons0/15 achievements
0/100 XP to next level100 XP to go0% complete

The 'commit and don't look back' group

An atomic group (?>...) matches like a normal group, BUT once it succeeds, the engine refuses to backtrack into it. Whatever it consumed is locked in. The engine moves forward; if the rest of the pattern fails, it doesn't try alternatives inside the atomic group.

Why this matters

Backtracking inside loops is the source of all catastrophic regex performance. (\w+)*X against a long string of word characters with no X at the end will try every possible split of the input among the iterations of \w+ — exponential.

Make the inner group atomic: (?>\w+)*X. Now once \w+ grabs everything it can, the engine doesn't try to give characters back. Fails immediately, linear time.

Engine support

  • Python re: Atomic groups added in Python 3.11. Earlier versions: install third-party regex.
  • PCRE, Perl, Java, Ruby, .NET: Supported.
  • JavaScript: Not supported (yet).
  • RE2/Go: Doesn't backtrack at all, so doesn't need atomic groups.

Mental model

Atomic groups are how you tell the engine "once you've matched this, don't second-guess yourself." Use them whenever a pattern's inner section won't have a valid alternative match if the first one fails — which is almost always for things like "one or more digits," "one or more word characters," "the rest of this line."

Code

Atomic groups vs vanilla·python
import re

# Vanilla — engine will backtrack inside (\w+)*
# This is the canonical ReDoS shape
# re.search(r'(\w+)*X', 'a' * 30)  # would hang for many seconds

# Atomic — refuses to backtrack into (?>\w+)
# Python 3.11+
import sys
if sys.version_info >= (3, 11):
    re.search(r'(?>\w+)*X', 'a' * 30)  # fails immediately

# Practical: extract until end-of-line, no backtracking
# Vanilla
re.match(r'(\w+)\s+(\w+)', 'hello world')

# Atomic — same result, faster on adversarial input
if sys.version_info >= (3, 11):
    re.match(r'(?>\w+)\s+(?>\w+)', 'hello world')

# When the next thing in the pattern fails, atomic wins big
# Match digits followed by 'kg', failing fast on bad input
for _ in range(100000):
    re.search(r'\d+kg', '1234567890123456789' * 100)  # slow due to backtracking
# vs
if sys.version_info >= (3, 11):
    for _ in range(100000):
        re.search(r'(?>\d+)kg', '1234567890123456789' * 100)  # fast

External links

Exercise

If you're on Python 3.11+: time re.search(r'(\w+)+X', 'a' * 30) versus re.search(r'(?>\w+)+X', 'a' * 30). The first should hang or take forever; the second should finish instantly. That difference is the point of atomic groups.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.