Lesson 5 of 6

Lookahead & Lookbehind

Sometimes what a piece of text needs to match depends on what's next to it, but you don't actually want that neighboring text included in your match. You want a number only if it's followed by "px," or a price only if it's preceded by "$" — but you want just the number, not the unit or the currency symbol along with it. That's exactly what lookahead and lookbehind assertions are for: checking context without consuming it.

💡 The Bottom Line

Lookahead (?=...) and lookbehind (?<=...) check that something does (or with a !, does not) appear right after or right before the current position — but neither one becomes part of the actual match. They're a way to require context without capturing it.

Positive lookahead: (?=...)

Matches a number, but only if it's immediately followed by "px" — without including "px" in the match itself:

const re = /\d+(?=px)/;

"width: 240px".match(re)[0]  // "240"  — matched, and "px" isn't part of it
"width: 240em".match(re)     // null   — no "px" right after the digits, so no match

The (?=px) part is a condition that must be true right after the digits — it never consumes or captures "px" itself, it just peeks ahead to check.

Negative lookahead: (?!...)

Matches a number, but only if it's not followed by "px":

const re = /\d+(?!px)/;

"240em".match(re)[0]  // "24"  — careful: this stops as soon as the rest doesn't look like "px",
                      //         which can be surprising with partial matches; test edge cases directly

Negative lookahead is especially useful for exclusion rules — like "match a word that is not immediately followed by a specific suffix."

Positive lookbehind: (?<=...)

Matches a number, but only if it's immediately preceded by "$" — again, without including the "$" itself in the match:

const re = /(?<=\$)\d+/;

"Total: $42".match(re)[0]   // "42" — matched, and "$" isn't part of the match
"Total: 42".match(re)       // null — no "$" right before the digits

This is the mirror image of lookahead: instead of checking what comes after the position, it checks what comes before it.

Negative lookbehind: (?<!...)

Matches a number, but only if it's not immediately preceded by "$":

const re = /(?<!\$)\d+/;

"42 items".match(re)[0]  // "42" — no "$" before it, so it matches
"$42".match(re)          // null — "$" precedes it, so negative lookbehind rejects it

📋 The four assertions at a glance

  • (?=...) — positive lookahead: must be followed by this
  • (?!...) — negative lookahead: must NOT be followed by this
  • (?<=...) — positive lookbehind: must be preceded by this
  • (?<!...) — negative lookbehind: must NOT be preceded by this

⚠️ Watch Out

None of these four assertions ever appear in the actual matched text — they're checks on the surrounding context only, sometimes called "zero-width assertions" because they don't consume any characters. If you need the surrounding text included in your result too, use a regular (capturing) group instead, not a lookaround.

🙋 There Are No Dumb Questions

Q: Is lookbehind supported everywhere, like lookahead is?
A: Lookahead has been widely supported for a long time. Lookbehind is newer in some engines — JavaScript only added lookbehind support in relatively recent versions (2018-era engines onward). If you need to support very old browsers or environments, double-check lookbehind support before relying on it; lookahead has essentially universal support.

🔧 Try It Yourself

Write a pattern that matches a word only when it is not immediately followed by an exclamation mark — so it matches "hello" in "hello world" but not the "hello" in "hello!". Hint: you'll want \w+ combined with a negative lookahead for !.

✅ Quick Check

What does (?<=\$)\d+ match in the string "Total: $42"?


Next up: the common gotchas that trip up almost everyone learning regex — greedy vs. lazy matching, catastrophic backtracking, and forgetting to escape special characters.

← Back to
Next →