What a Lookbehind Does
A lookbehind is the mirror image of a lookahead. Instead of peeking forward, the engine checks what came before the current position. Like a lookahead, it is zero-width: the checked text is not consumed and does not appear in the match result.
The practical use case is extracting a value that is always preceded by a known label or delimiter, without including that label in your result.
Positive Lookbehind (?<=...)
Syntax: (?<=CONDITION)PATTERN
The engine checks whether CONDITION immediately precedes the current position. If it does, it proceeds to match PATTERN.
Example — extract dollar amounts without the currency symbol:
Pattern: (?<=\$)\d+(\.\d{2})?
String: Total: $149.99 → Matches: 149.99
Discount: $20 → Matches: 20
Code: 200 → No match (no $ prefix)
Operational example — extract values from structured log fields:
Pattern: (?<=latency_ms=)\d+
String: ts=2024-01-15 level=info latency_ms=342 status=200
Matches: 342
This is far more precise than splitting on spaces and hoping the field order never changes.
Negative Lookbehind (?<!...)
Syntax: (?<!CONDITION)PATTERN
The engine checks that CONDITION does not immediately precede the current position.
Example — match "ID:" references, but not "Group ID:":
Pattern: (?<!Group )ID: \d+
String: User ID: 123 → Match: ID: 123
Group ID: 456 → No match
Session ID: 789 → Match: ID: 789
Operational example — match port numbers not preceded by a colon (i.e., not part of an IPv6 address):
Pattern: (?<!:)\b\d{4,5}\b
String: Connect to host:8080 → Matches: 8080
IPv6: ::1:443 → No match for 443 (preceded by :)
Engine Support and Constraints
Lookbehind support varies significantly across regex engines:
| Engine | Positive Lookbehind | Negative Lookbehind | Variable-length Lookbehind |
|---|---|---|---|
Python (re) |
✅ | ✅ | ❌ (fixed-width only) |
Python (regex module) |
✅ | ✅ | ✅ |
| JavaScript (ES2018+) | ✅ | ✅ | ✅ |
Go (regexp) |
❌ | ❌ | ❌ |
| PCRE / PHP | ✅ | ✅ | ❌ (fixed-width only) |
| Java | ✅ | ✅ | ✅ (since Java 8) |
The most important constraint: in most engines, the lookbehind pattern must be fixed-width. You cannot write (?<=\w+) because \w+ is variable length. You must write (?<=\w{1,10}) or restructure the pattern. The regex module in Python and modern JavaScript are exceptions.
In Go, lookbehinds are entirely absent. The idiomatic Go approach is to use a capturing group and extract group 1: latency_ms=(\d+) then read match[1].
A common interview scenario: "You need to parse structured log lines in Go and extract field values. How do you handle the lack of lookbehind support?" The correct answer is to use a capturing group — fieldname=(\d+) — and reference the first capture group from the match result. This is functionally equivalent to a lookbehind for extraction purposes. Bonus points for mentioning that Go's regexp package uses RE2 syntax, which deliberately excludes features that cannot be evaluated in linear time, making it immune to ReDoS attacks by design.