Lookbehind
Regex Lookbehind — The Complete Guide
Master positive and negative lookbehind assertions to match text based on what precedes it — without consuming those characters.
Introduction
A lookbehind is a regex assertion that checks whether the text at the current position is (or is not) preceded by a given pattern — without including that preceding pattern in the match. It's the mirror image of a lookahead: instead of peeking forward, it glances backward. This "zero-width" behaviour lets you extract or validate text based on the context before it, such as grabbing a number only when it follows a currency symbol.
Real-World Analogy
The Rear-View Mirror
A driver glances in the rear-view mirror to check what's behind before changing lanes — but that glance doesn't move the car backward. A lookbehind is that mirror: it verifies what precedes the match to allow it, without stepping back over any characters.
Prerequisites
Before You Start
- Python 3.x installed
- Understanding of the
remodule (search, findall, sub) - Familiarity with groups, tokens (
\d,\w), and escaping special characters - Comfort using raw strings
r"..."for patterns - Helpful: prior knowledge of lookahead assertions
The Key Idea: Zero-Width
Like all lookarounds, a lookbehind is zero-width. It tests a condition about the characters before the current position but does not consume them or include them in the result — the matched text starts right after the lookbehind succeeds.
The Two Lookbehind Assertions
Positive Lookbehind (?<=...)
Match only if preceded by a pattern.
(?<=Y)X matches X only when it is immediately preceded by Y. Y is not part of the match.
Negative Lookbehind (?<!...)
Match only if NOT preceded by a pattern.
(?<!Y)X matches X only when it is not immediately preceded by Y.
Syntax Reference
| Syntax | Name | Matches X when... |
|---|---|---|
(?<=Y)X |
Positive lookbehind | X is preceded by Y |
(?<!Y)X |
Negative lookbehind | X is not preceded by Y |
Positive Lookbehind (?<=...)
Extract a number only when it follows a dollar sign — without capturing the sign.
import re
text = "$100 and €200 and $300"
# \d+ that is preceded by "$" — but "$" is NOT part of the match
dollars = re.findall(r"(?<=\$)\d+", text)
print(dollars) # ['100', '300']
$ was checked but excluded from the match.
Negative Lookbehind (?<!...)
Extract numbers that are NOT preceded by a dollar sign.
import re
text = "$100 and €200 and $300"
# numbers NOT preceded by "$"
non_dollars = re.findall(r"(?<!\$)\b\d+", text)
print(non_dollars) # ['200']
Python's Fixed-Width Limitation
The biggest gotcha: in Python's built-in re module, a lookbehind must be fixed-width — every alternative
must have the same, known length. Variable-length quantifiers (+, *, {2,4}) are not allowed.
import re
# OK — fixed width (exactly 3 characters)
re.findall(r"(?<=abc)\d+", "abc123") # works
# ERROR — variable width
# re.findall(r"(?<=a+)\d+", "aaa123")
# raises: re.error: look-behind requires fixed-width pattern
(?<=\d+), (?<=ab?c), and (?<=colou?r) — all variable-width, all raise errors in re.
regex module (pip install regex) which fully supports variable-width lookbehind.
import regex # third-party module
# Variable-width lookbehind works here
print(regex.findall(r"(?<=a+)\d+", "aaa123")) # ['123']
Alternation Must Be Equal Width
Alternatives inside a lookbehind are allowed only if they are all the same length.
import re
# OK — both alternatives are 3 characters
print(re.findall(r"(?<=Mr\.|Ms\.)\s?\w+", "Mr. Smith")) # note: Mr. and Ms. both length 3+dot
# Both "cat" and "dog" are 3 chars -> allowed
print(re.findall(r"(?<=cat|dog)\d+", "cat42 dog99")) # ['42', '99']
Using Lookbehind in Replacements
Mask digits that appear after a label, keeping the label intact.
import re
text = "PIN:1234 CODE:5678"
# Replace digits that follow "PIN:" with asterisks
masked = re.sub(r"(?<=PIN:)\d", "*", text)
print(masked) # PIN:**** CODE:5678
Lookbehind vs Lookahead
| Aspect | Lookbehind | Lookahead |
|---|---|---|
| Direction | Checks text before | Checks text after |
| Positive syntax | (?<=...) |
(?=...) |
| Negative syntax | (?<!...) |
(?!...) |
| Position in pattern | Before the main token | After the main token |
| Width in Python re | Must be fixed-width | Can be variable-width |
Zero-Width, Backward
A lookbehind at position \(p\) inspects the characters in the interval ending exactly at \(p\). For a fixed-width lookbehind of length \(w\), it examines the slice \([\,p-w,\; p\,)\) but leaves the match position unchanged:
\[ \text{inspect } [\,p-w,\; p\,) \quad\Rightarrow\quad p_{\text{after}} = p_{\text{before}} \]
Because \(w\) must be a single constant in Python's re, the engine always knows exactly how far back to look.
Best Practices
Do This
- Keep Python lookbehind fixed-width; switch to the
regexmodule for variable-width - Escape special characters like
$,., and(inside the lookbehind - Use lookbehind to exclude a prefix (like
$) from the result - Combine with
\bto avoid partial matches - Prefer lookbehind in
re.subto transform text after a label without touching the label - Ensure all alternation branches have equal length
Common Mistakes
(?<=\w+) in re — it raises a "fixed-width pattern" error.
regex module for variable width.
$: (?<=$) treats $ as end-of-string, not a literal dollar sign.
(?<=\$) to match a literal dollar sign before your target.
Interview Questions
| Question | Short Answer |
|---|---|
| What is a lookbehind? | A zero-width assertion that checks whether the current position is preceded by a pattern. |
| Positive vs negative lookbehind? | (?<=...) requires the preceding pattern; (?<!...) forbids it. |
Key limitation in Python's re? |
Lookbehind must be fixed-width — no variable quantifiers. |
| How to get variable-width lookbehind? | Use the third-party regex module instead of re. |
| How does it differ from lookahead? | Lookbehind checks text before; lookahead checks text after — both zero-width. |
Quick Revision
| Assertion | Syntax | Meaning |
|---|---|---|
| Positive lookbehind | (?<=...) |
Preceded by |
| Negative lookbehind | (?<!...) |
Not preceded by |
| Width rule (re) | Fixed-width only | No + * {n,m} |
| Variable width | regex module |
Allowed there |
Key Takeaways
A lookbehind is a zero-width assertion that matches text by what precedes it —
using (?<=...) for "preceded by" and (?<!...) for "not preceded by". Remember Python's re
requires fixed-width lookbehind; reach for the regex module when you need variable width.