Negative look-ahead is a zero-width assertion in Python’s re module that matches a position in the string only if it is NOT immediately followed by a specified pattern — without consuming or including any following text in the match. The syntax is (?!pattern), where pattern is the forbidden content that must not appear next. Negative look-ahead is ideal for exclusionary matching: match something only if it’s not followed by unwanted context (e.g., words not followed by punctuation, numbers not followed by units, keywords not followed by certain delimiters). In 2026, negative look-ahead remains a key regex feature — essential in data validation, text filtering, cleaning (e.g., exclude false positives), log parsing, and vectorized pandas/Polars string operations where conditional exclusions scale efficiently across large datasets without extra post-processing.
Here’s a complete, practical guide to negative look-ahead in Python regex: syntax and mechanics, examples, real-world patterns, and modern best practices with raw strings, flags, compilation, and pandas/Polars integration.
Negative look-ahead (?!pattern) succeeds only if the current position is NOT followed by pattern — the match itself stops before the look-ahead check.
import re
text = "The foo is not followed by bar, but it is followed by baz."
# Match "foo" only if NOT followed by "bar"
pattern = r"foo(?!bar)"
matches = re.findall(pattern, text)
print(matches) # ['foo']
# Match words NOT followed by punctuation
print(re.findall(r'\w+(?![.,!?])', "Hello, world! How are you?")) # ['How', 'are']
Negative look-ahead with quantifiers — excludes matches in unwanted contexts while keeping the match clean.
# Match numbers NOT followed by "px" (exclude CSS units)
css = "font-size: 16px; margin: 20 10px;"
print(re.findall(r'\d+(?!px)', css)) # ['20', '10'] (16px is excluded)
# Match "error" only if NOT followed by ":"
logs = "ERROR: failed INFO: success WARNING: low memory ERROR without colon"
print(re.findall(r'ERROR(?!:)', logs)) # ['ERROR'] (only the one without colon)
Real-world pattern: conditional filtering and extraction in pandas — negative look-ahead matches only when unwanted context is absent, without capturing extra text.
import pandas as pd
df = pd.DataFrame({
'text': [
"Price: $99.99 (discounted)",
"Value: 50",
"Total: €200.00",
"Discount: $10"
]
})
# Extract amounts NOT in dollars (negative look-ahead excludes $)
df['non_dollar'] = df['text'].str.extract(r'(?
Best practices make negative look-ahead safe, readable, and performant. Use negative look-ahead for exclusion conditions — foo(?!bar) — matches only when “bar” does not follow. Combine with quantifiers — \d+(?!px) — to exclude specific units/contexts. Modern tip: use Polars for large text columns — pl.col("text").str.extract(r'(? is 10–100× faster than pandas .str.extract(). Add type hints — str or pd.Series[str] — improves static analysis. Use raw strings r'pattern' — avoids double-escaping backslashes. Compile patterns with re.compile() for repeated use — faster and clearer. Combine with pandas.str — df['col'].str.contains(r'pattern(?!forbidden)', regex=True) for vectorized exclusion checks. Use negative look-ahead to avoid false positives — keeps matches precise. Avoid overuse — look-ahead can slow matching; test performance on large data.
Negative look-ahead ((?!...)) matches only if NOT followed by a pattern — without consuming or capturing the following text. In 2026, use it for exclusions, raw strings, compile patterns, and vectorize in pandas/Polars. Master negative look-ahead, and you’ll create precise, exclusion-based regex patterns for validation, filtering, and cleaning.
Next time you need to match only if NOT followed by something — use negative look-ahead. It’s Python’s cleanest way to say: “Match this, but only if this does NOT come next.”