(?!...): negative lookahead for exclusion rules
(?!...) succeeds at any position where the pattern inside it does not match, and consumes nothing. It is how you express a rule about what must not come next.
At a glance
- Syntax
- (?!...)
- Succeeds when
- the inner pattern does not match at this position
- Width
- zero
- Line level exclusion
- ^(?!.*word).*$ for a line that does not contain a word
- Classic bug
- a greedy quantifier in front of it backtracks until the assertion passes
- Captures inside
- never keep a value, because the branch has to fail
In detail
The straightforward use is at an anchor. /^(?!.*error).*$/ accepts any line that does not contain the word error, because the assertion runs once at the start of the line and scans the whole of it. Written without the anchor the same assertion is nearly useless, since it will find some position further along where the word does not follow and succeed there.
The subtle failure is what happens when a quantifier sits in front of it. /\d+(?!%)/ looks like a rule against percentages, and against the string "50%" it matches the single digit 5. The greedy \d+ takes both digits, the assertion fails because a percent sign follows, and the engine simply backtracks to one digit, at which point the next character is a zero and the assertion is satisfied.
The fix is to close off the escape route. /\d+(?![\d%])/ says the next character may be neither another digit nor a percent sign, which leaves the engine nowhere to backtrack to, and the pattern now correctly rejects the whole of "50%" while still matching the standalone number in "50 60%". A word boundary before the assertion achieves something similar.
Captures placed inside a negative lookahead are effectively write only. The assertion succeeds precisely when its contents failed to match, so any group in there ends up undefined. That is occasionally useful as a way of grouping without a value and much more often a sign that the pattern is not doing what its author thinks.
Examples
const clean = /^(?!.*error).*$/;
clean.test("all good"); // true
clean.test("fatal error here"); // false/\d+(?!%)/.exec("50%")[0]; // "5", not what anyone wanted
/\d+(?![\d%])/.exec("50%"); // null
"50 60%".match(/\d+(?![\d%])/g); // ["50"]Things that catch people out
- A greedy quantifier in front of a negative lookahead will give characters back until the assertion passes, so /\d+(?!%)/ matches part of a percentage rather than rejecting it.
- An unanchored negative lookahead is almost always too weak, because there is usually some position in the string where the forbidden text does not follow.
- Groups inside a negative lookahead never keep a value, since the assertion only succeeds when the contents failed.
Try it: Regex Tester
Type a pattern and a test string, watch every match highlight as you go, read a plain English breakdown of each token, and check the numbered and named groups it captured.
Regex Tester
LOCAL · Browser-OnlyTest and debug regular expressions with live matching and highlights
Related Regex syntax
All regex syntax →(?=...): positive lookahead for zero width tests
A lookahead asserts what comes next without consuming it, which is how several independent rules can all be checked from the same position.
(?<!...): negative lookbehind to exclude a prefix
A negative lookbehind succeeds when the text before the position does not match. At index zero it always succeeds, which is usually but not always right.
Lazy quantifiers: *?, +?, ?? and {n,m}?
A trailing question mark makes a quantifier prefer the shortest match. It fixes the overreaching tag pattern, and a negated class often fixes it better still.
(?<=...): positive lookbehind and where it runs
JavaScript allows variable length lookbehind, which Java does not, and matches its contents from right to left. RE2 based engines have none at all.
Alternation: precedence and ordered choice
The pipe has the lowest precedence of any regex operator and the branches are tried in order, so ^cat|dog$ and /a|ab/ both surprise people.
\n: matching a newline, and the double escape trap
\n matches the line feed character. The trap is building a pattern from a string, where the number of backslashes decides what the engine actually sees.
Tools for this
- Regex Tester Free online regex tester - test and debug regular expressions with live matching and highlights
- Regex Playground Regex playground with live match highlighting, named groups, find and replace, and shareable links