Lookahead and lookbehind
Assert what must come before or after a match without including it, and build validation patterns that stay readable.
Lookahead
A lookahead is a zero-width assertion about what follows the current position. Because it consumes nothing, the inspected text is still available for the rest of the pattern and for replacements.
// positive: must be followed by
/\d+(?=px)/.exec('12px 40em')[0]; // '12', the px is not consumed
// negative: must not be followed by
/\d+(?!px)/.exec('12px 40em')[0]; // '40'
// several lookaheads at one position behave as an AND
const strong = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/;
strong.test('Passw0rd'); // true
strong.test('password1'); // false, no uppercase
// a capture inside a lookahead is still recorded
/(?=(\d{3}))(\d+)/.exec('12345').slice(1); // [ '123', '12345' ]
// add a comma to a number without consuming the digits
'1234567'.replace(/\B(?=(\d{3})+(?!\d))/g, ','); // '1,234,567'- Lookarounds are zero-width, so consecutive lookaheads all inspect the same suffix. That is what makes the password AND-chain work.
- Lookahead is available in every engine in common use: JavaScript, Python, Java, PCRE, .NET and RE2.
- A lookahead is not a substitute for consuming input. If you need the text in the result, capture it instead of asserting on it.
Lookbehind and its limits
import re
re.search(r'(?<=\$)\d+', 'price $42').group() # '42'
re.search(r'(?<!\$)\d+', 'price $42, qty 3').group() # '3'
# fixed width only: every alternative must have the same length
re.search(r'(?<=cat|dog)nap', 'a catnap') # fine, both are three characters
re.search(r'(?<=cat|cats )nap', 'the cats nap') # raises re.error: widths differ| Engine | Lookbehind support |
|---|---|
| JavaScript (ES2018 and later) | yes, variable length |
| Python 3 | yes, but the width must be fixed |
| Java | yes, bounded and unbounded forms |
| PCRE2 | yes, variable length |
| .NET | yes, variable length |
| RE2 and Go | no lookbehind at all |
Fixed width means every alternative must match the same number of characters: (?<=cat|dog) is fine while (?<=cat|dogs) raises an error in Python. Rewrite that as two patterns, or capture the prefix and strip it afterwards.
Overlapping matches
Normal matching resumes after the end of the previous match, so /aa/g finds two occurrences in a four-letter run. When you need overlapping results you have to advance the search position by one character yourself.
function overlapping(text, re) {
const out = [];
let i = 0;
while (i <= text.length - 1) {
re.lastIndex = i;
const m = re.exec(text);
if (!m) break;
out.push(m[0]);
i = m.index + 1; // NOT m.index + m[0].length
}
return out;
}
overlapping('aaaa', /aa/g); // [ 'aa', 'aa', 'aa' ]
overlapping('banana', /ana/g); // [ 'ana', 'ana' ]test calls over one pattern holding five lookaheads — it is easier to read and far easier to debug.FAQ
When should I use a lookaround instead of a capture group?
Why does my lookbehind throw in Python but work in JavaScript?
Related
Groups, captures and backreferences Flags and matching modes
Last refreshed 2026-09-18.