RX

Lookahead & Lookbehind

Regular Expressions · 6 entries

Positive Lookahead (?=)

syntax
pattern(?=ahead)  matches pattern only if followed by ahead
example
JS:  '100px 200em 50px'.match(/\d+(?=px)/g)
Py:  re.findall(r'\d+(?=px)', '100px 200em 50px')
output
["100", "50"]  (numbers followed by 'px', but 'px' is not in the match)

Note Lookaheads are zero-width assertions -- they check what comes next without consuming characters. The looked-ahead text is not part of the match result. This means subsequent patterns can still match those characters.

Negative Lookahead (?!)

syntax
pattern(?!ahead)  matches pattern only if NOT followed by ahead
example
JS:  '3 dogs 4 cats 5 dogs'.match(/\d+(?! cats)/g)
Py:  re.findall(r'\b\w+\.(?!exe\b)\w+', 'run.exe data.csv img.png')
output
JS: ["3", "5"]  (numbers NOT followed by ' cats')
Py: ["data.csv", "img.png"]  (files that are NOT .exe)

Note Negative lookahead is excellent for excluding specific patterns. Watch for off-by-one issues -- ensure the lookahead checks the right position. Commonly used to exclude file types, keywords, or invalid suffixes.

Positive Lookbehind (?<=)

syntax
(?<=behind)pattern  matches pattern only if preceded by behind
example
JS:  '$100 and EUR200'.match(/(?<=\$)\d+/g)
Py:  re.findall(r'(?<=@)\w+', 'user@gmail and admin@corp')
output
JS: ["100"]  (digits preceded by $)
Py: ["gmail", "corp"]  (words preceded by @)

Note Lookbehinds check what comes BEFORE the match position without including it. In JavaScript, lookbehinds were added in ES2018 and are supported in all modern browsers. Python's re module requires fixed-width lookbehinds -- use the third-party regex module for variable-width.

Negative Lookbehind (?<!)

syntax
(?<!behind)pattern  matches pattern only if NOT preceded by behind
example
JS:  'USD 100 and $200'.match(/(?<!\$)\b\d+/g)
Py:  re.findall(r'(?<!\\)\n', 'line1\nline2\\\nline3')
output
JS: ["100"]  (digits NOT preceded by $)

Note Useful for avoiding matches that follow a specific prefix. A classic use case is matching newlines that are not escaped (not preceded by a backslash).

Combining Lookaheads & Lookbehinds

syntax
(?<=prefix)pattern(?=suffix)
example
JS:  'price: [42] and size: [XL]'.match(/(?<=\[)\d+(?=\])/g)
Py:  re.findall(r'(?<=\[)\w+(?=\])', '[hello] and [world]')
output
JS: ["42"]  (digits inside square brackets, without brackets)
Py: ["hello", "world"]

Note You can combine multiple lookarounds on the same position. A common pattern for password validation stacks multiple lookaheads: (?=.*[A-Z])(?=.*\d)(?=.*[@#$]).{8,} checks for uppercase, digit, and special character all in one pass.

Stacked Lookaheads for Validation

syntax
^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[!@#$%]).{8,}$
example
JS:  const strong = /^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[!@#$%]).{8,}$/
     strong.test('MyP@ss1word')   // true
     strong.test('weakpass')       // false
Py:  pattern = r'^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[!@#$%]).{8,}$'
     bool(re.match(pattern, 'MyP@ss1word'))  # True
output
true / True for 'MyP@ss1word', false / False for 'weakpass'

Note Each lookahead checks a different requirement without advancing the match position. All must pass before .{8,} consumes the string. This is a widely-used pattern but be cautious about catastrophic backtracking with very long inputs -- consider checking each requirement separately in code for production validation.