Negative lookahead
Assert that a pattern is NOT followed by specific content using (?!…) — the complement to positive lookahead.
- Write a negative lookahead using (?!…)
- Use negative lookahead to exclude specific suffixes or following contexts
- Distinguish negative lookahead from a negated character class
A negative lookahead (?!…) is the inverse of (?=…). It asserts that
the text at the current position is not followed by the specified pattern.
Like all lookarounds, it is zero-width — it checks a condition without consuming
characters.
Syntax and basic usage
// Match "file" when NOT followed by ".exe"
/\bfile(?!\.exe)\b/i.test("file.txt"); // true
/\bfile(?!\.exe)\b/i.test("file.exe"); // false
/\bfile(?!\.exe)\b/i.test("file.docx"); // trueThe assertion (?!\.exe) gates the match: the engine checks whether .exe
follows, and only proceeds if it does not.
Practical examples
Match JavaScript keywords not followed by a colon (not object keys):
const code = "if (condition) { return: true; }";
code.match(/\breturn(?!:)/g); // ["return"] — only the real keywordMatch http not followed by s (to find non-HTTPS links):
const html = 'href="http://old.com" href="https://new.com"';
html.match(/https?:\/\/(?!.*https)[\w.]+/g);
// Simpler approach: match http:// but not https://
html.match(/\bhttp(?!s):\/\/[\w.]+/g); // ["http://old.com"]Match node not followed by _modules:
const paths = ["node_modules/react", "node/index.js", "node_modules/lodash"];
paths.filter(p => /^node(?!_modules)/.test(p));
// ["node/index.js"]Negative lookahead vs negated character class
It is important to understand when to use [^…] versus (?!…). They are
different tools:
| Technique | What it does |
|---|---|
[^abc] | Matches one character that is not a, b, or c |
(?![abc]) | Asserts the next character is not a, b, or c — then the main pattern still consumes it |
These two are often interchangeable for single-character exclusions, but (?!…)
can look ahead multiple characters:
// Exclude the two-char sequence "ex" from following
/\w+(?!ex)\b/ // awkward with character class alone
/\w+(?!\.exe)/ // "not followed by .exe" — impossible with [^…](?!…) is zero-width — it does not consume the character it checks. After a
negative lookahead succeeds, the engine is still at the same position and the
next part of the pattern continues from there. This means (?!a). matches any
character that is not a: the lookahead checks, and then . consumes whatever
character is actually there.
Combining with word boundaries
Word boundaries and lookaheads work well together:
// "get" not part of "getter" or "getaway" etc.
/\bget(?![A-Za-z])\b/.test("get(x)"); // true — "get" alone
/\bget(?![A-Za-z])\b/.test("getter"); // false — followed by letter(?!…) in Python
Syntax is identical across engines. Python example:
import re
pattern = re.compile(r'\bfile(?!\.exe)\b', re.IGNORECASE)
pattern.search("file.txt") # match
pattern.search("file.exe") # no matchWhere to go next
Next: lookbehind — (?<=…) and (?<!…) make the same kind of assertions
about what precedes the match.