C.W.K.
Stream
Lesson 05 of 10 · published

Negative Lookbehind — (?<!...)

~8 min · lookbehind, negative

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

Assert that something does NOT precede

(?<!...) is the negative form of lookbehind: "at this position, the pattern inside does NOT match the text before." Match succeeds only if the lookbehind pattern would fail.

The most common use: skip references to escaped or commented-out content

"Find every TODO comment that is NOT prefixed with 'DONE-'." The negative lookbehind asserts the unwanted prefix is absent. Without it, you'd match everything and filter in code.

Real example: sentence-boundary detection

Pattern: (?<!Mr|Mrs|Dr)\.\s+[A-Z] — a period followed by space and capital letter, but only if it's NOT preceded by a title abbreviation. Sloppy sentence boundaries usually trip on 'Mr. Smith said' being treated as two sentences. Negative lookbehind fixes that.

Same width restrictions as positive lookbehind

Python re wants fixed-width here too. The pattern (?<!Mr|Mrs|Dr) has alternatives of different widths (2, 3, 2) — Python's built-in re may raise an error on this. Solutions: install regex module, OR pad to fixed width by repeating, OR rethink as positive matching with later filtering.

Interaction with anchors

(?<!^) means "not at the start of the string." Combined with anchors, lookbehind unlocks position-sensitive patterns: "find this token but not at the very start of the line," "find quotes that aren't at the very start."

Code

Negative lookbehind patterns·python
import re

# Find numbers NOT preceded by '$' (skip prices)
re.findall(r'(?<!\$)\d+', 'cost $20 plus 5 shipping and $3 tax')
# ['5']  — '20' and '3' excluded because they have '$'

# Word 'cat' not preceded by 'a '
re.findall(r'(?<!a )cat', 'a cat saw a cat and the cat ran')
# ['cat']  — only the third (preceded by 'the ')

# 'TODO' not preceded by 'DONE-'
re.findall(r'(?<!DONE-)TODO', 'TODO fix this DONE-TODO already done')
# ['TODO']

# Number not at start of line (with re.MULTILINE)
re.findall(r'(?<!^)\d+', '5 a 10 b 15', flags=re.MULTILINE)
# ['10', '15']  — '5' is at start of line (only line)

External links

Exercise

Write a regex that finds the word 'fix' in source code BUT NOT when it's preceded by '// ' or '# ' (i.e., not in a comment). Test on a small Python or JS snippet.

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.