Lookahead and Lookbehind Assertions
A visual guide to regex zero-width assertions -- match based on context without consuming characters.
What Are Lookaround Assertions?
Lookahead and lookbehind (collectively called "lookaround") are zero-width assertions in regular expressions. Unlike normal regex elements that consume characters as they match, lookaround assertions check what is ahead of or behind the current position without including those characters in the match result. Think of them as conditional checks: "match this, but only if it's followed by (or preceded by) that."
There are four types of lookaround, organized along two axes: direction (ahead or behind) and polarity (positive or negative). Understanding these four types unlocks patterns that would be impossible -- or at least incredibly awkward -- to express otherwise.
| Type | Syntax | Meaning |
|---|---|---|
| Positive lookahead | (?=...) | What follows must match |
| Negative lookahead | (?!...) | What follows must NOT match |
| Positive lookbehind | (?<=...) | What precedes must match |
| Negative lookbehind | (?<!...) | What precedes must NOT match |
Positive Lookahead: (?=...)
A positive lookahead asserts that what immediately follows the current position matches the pattern inside the parentheses. The key insight is that the lookahead checks the upcoming characters but does not advance the regex engine's position or include those characters in the match.
Example: Extract Prices in USD
Suppose you want to match numbers that are followed by " USD" but you only want to capture the number, not the currency label:
Pattern: \d+(?= USD)
String: "100 USD, 200 EUR, 300 USD"
Matches: "100" and "300"Step by Step
- 1. Engine starts at position 0.
\d+matches "100". - 2. Lookahead
(?= USD)checks: is " USD" next? Yes. Match succeeds: "100". - 3. Engine moves forward.
\d+matches "200". - 4. Lookahead checks: is " USD" next? No, it's " EUR". Match fails. Skip.
- 5. Engine moves forward.
\d+matches "300". - 6. Lookahead checks: is " USD" next? Yes. Match succeeds: "300".
Notice that " USD" is not part of the match result -- the lookahead verified its presence but did not consume it. This is the defining characteristic of all lookaround assertions.
Negative Lookahead: (?!...)
A negative lookahead is the inverse: it asserts that what follows does NOT match the pattern inside. This is useful for exclusion patterns -- "match this, but not when followed by that."
Example: Match Words Not Followed by a Comma
Pattern: \w+(?!,)\b
String: "apple, banana cherry, date"
Matches: "banana" and "date"
(Note: "apple" and "cherry" are followed by commas)Example: Validate Passwords
One of the most common practical uses of negative (and positive) lookahead is password validation. By stacking multiple lookaheads at the start of the pattern, you can enforce multiple rules simultaneously:
Pattern: ^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$
Rules enforced:
(?=.*[A-Z]) - At least one uppercase letter
(?=.*[a-z]) - At least one lowercase letter
(?=.*\d) - At least one digit
(?=.*[!@#$%^&*]) - At least one special character
.{8,} - Minimum 8 characters totalEach lookahead starts from the same position (the beginning of the string) and checks one requirement independently. Because they are zero-width, they don't interfere with each other. After all lookaheads pass, .{8,} matches the actual characters.
Positive Lookbehind: (?<=...)
A positive lookbehind asserts that what immediately precedes the current position matches the pattern inside. It looks backward instead of forward.
Example: Extract Prices After a Dollar Sign
Pattern: (?<=\$)\d+\.?\d*
String: "Price: $42.99 or €35.00"
Matches: "42.99"Step by Step
- 1. Engine reaches the "4" in "$42.99".
- 2. Lookbehind
(?<=\$)checks: is there a "$" before this position? Yes. - 3.
\d+\.?\d*matches "42.99". The "$" is NOT included in the result. - 4. Engine reaches "3" in "35.00". Lookbehind checks for "$": the preceding character is "?" (from "?"). No match.
Example: Parse Log Levels
Extract the message after a log level prefix:
Pattern: (?<=\[ERROR\] ).+
String: "[ERROR] Connection timed out"
Matches: "Connection timed out"Negative Lookbehind: (?<!...)
A negative lookbehind asserts that what precedes does NOT match. This completes the set of four lookaround types.
Example: Match Standalone Numbers
Match numbers that are NOT preceded by a dollar sign (not prices):
Pattern: (?<!\$)\b\d+\b
String: "I have $50 and 30 apples"
Matches: "30"Example: Avoid Matching Inside Strings
Match a keyword that is not inside a quoted string (simplified):
Pattern: (?<!")\bSELECT\b(?!")
String: 'Execute SELECT from "SELECT" table'
Matches: The first "SELECT" (the keyword), not the quoted oneCombining Lookahead and Lookbehind
You can combine lookahead and lookbehind in the same pattern for precise context matching. Here are two practical examples:
Extract Content Between Delimiters
Match the text between square brackets without including the brackets:
Pattern: (?<=\[)[^\]]+(?=\])
String: "config[database] and config[cache]"
Matches: "database" and "cache"Add Thousand Separators
This classic trick inserts commas into large numbers using lookahead and lookbehind:
Pattern: (?<=\d)(?=(\d{3})+(?!\d))
Replace: ","
String: "1234567890"
Result: "1,234,567,890"
How it works:
(?<=\d) - Must be preceded by a digit
(?=(\d{3})+(?!\d)) - Must be followed by groups of 3 digits
with no further digits afterThis is a powerful example of how lookaround enables operations that would require complex procedural code otherwise.
Common Pitfalls
Lookbehind Length Restrictions
In many regex flavors (including JavaScript before ES2018), lookbehind requires a fixed-length pattern. You cannot use *, +, or {n,m} inside a lookbehind:
# This works (fixed length):
(?<=abc)\d+
# This may NOT work in older engines (variable length):
(?<=a+)\d+
# Modern JavaScript (ES2018+) DOES support variable-length lookbehind
# But older browsers and some other languages do notCatastrophic Backtracking
Complex lookaround patterns with nested quantifiers can trigger catastrophic backtracking, causing the regex engine to hang. Be especially cautious with patterns like:
# DANGEROUS: nested quantifiers in lookahead
(?=(a+)+b)
# SAFE: use atomic groups or possessive quantifiers where available
# In JavaScript, restructure to avoid the nestingOrder Matters
When combining multiple lookaheads, the order does not affect correctness (they all check from the same position), but it does affect performance. Put the most restrictive or most likely to fail check first, so the engine can reject non-matches faster:
# Better: check the rarer condition first
^(?=.*[!@#$%^&*])(?=.*\d)(?=.*[A-Z]).{8,}$
# Rather than checking the common condition first
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$Browser and Engine Support
Lookahead has been supported in JavaScript since the beginning. Lookbehind was added in ECMAScript 2018 (ES9). Here is the current support landscape:
| Feature | Chrome | Firefox | Safari | Node.js |
|---|---|---|---|---|
| Positive lookahead (?=...) | All versions | All versions | All versions | All versions |
| Negative lookahead (?!...) | All versions | All versions | All versions | All versions |
| Positive lookbehind (?<=...) | 62+ | 78+ | 16.4+ | 8.10+ |
| Negative lookbehind (?<!...) | 62+ | 78+ | 16.4+ | 8.10+ |
As of 2026, lookbehind is supported in all modern browsers and Node.js versions. If you need to support older environments, stick to lookahead-only patterns or use alternative approaches.
When to Use Lookaround
Lookaround is the right tool when you need to:
- Match based on context: Match a pattern only when it appears in a specific context (after a dollar sign, before a keyword, etc.).
- Exclude from the match: Match a pattern but exclude the surrounding context from the result, avoiding the need for capture groups.
- Enforce multiple conditions: Stack lookaheads to validate multiple rules at the same position (password validation).
- Perform zero-width splits or replacements: Insert characters at positions (like thousand separators) without removing existing characters.
When the logic becomes too complex for lookaround alone, consider breaking the regex into multiple simpler patterns or using procedural code instead.
Try all of these patterns live with our Regex Tester. Paste any lookaround pattern and a test string to see exactly which parts match and get a plain-English explanation.
Further Reading
- MDN Assertions guide
MDN reference for boundary assertions and lookahead/lookbehind syntax.
- regular-expressions.info — Lookaround
In-depth tutorial on all four lookaround assertion types.