Code of the Day
IntermediateLookarounds

Negative lookahead

Assert that a pattern is NOT followed by specific content using (?!…) — the complement to positive lookahead.

Regular ExpressionsIntermediate7 min read
Recommended first
By the end of this lesson you will be able to:
  • 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");  // true

The 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 keyword

Match 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:

TechniqueWhat 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 match
JavaScript — editable, runs in your browser

Where to go next

Next: lookbehind(?<=…) and (?<!…) make the same kind of assertions about what precedes the match.

Finished reading? Mark it complete to track your progress.

On this page