Monu Tools

Lookahead and Lookbehind: Matching by Context

By Monu ToolsLast updated June 30, 2026

Sometimes you want to match something only because of what surrounds it, without making that context part of the match. A price, but only the digits after the currency sign. A word, but only if a certain word does not follow. Lookarounds do exactly this: they test the text next to the current position and then step back, matching nothing themselves. Try each example in the Regex Tester to see what is and is not included in the match.

Try the Regex Tester toolTest a regular expression against your text in real time. See every match highlighted, with capture groups and flags. Runs entirely in your browser.

Zero-width: assertions that match nothing

A lookaround is a zero-width assertion. Like the anchors ^ and $, it matches a position rather than a character. It looks at the characters ahead of or behind the current position, succeeds or fails based on what it sees, and then leaves the position unchanged. Because it consumes no characters, the text it inspected does not become part of the result. That is the whole trick, and it is what makes lookarounds so useful for matching by context.

The four lookarounds

FormNameSucceeds when
(?=...)Positive lookaheadthe text ahead matches
(?!...)Negative lookaheadthe text ahead does not match
(?<=...)Positive lookbehindthe text behind matches
(?<!...)Negative lookbehindthe text behind does not match

Lookahead: assert what follows

Positive lookahead shines for checking several independent conditions at one position, which is how password rules are usually expressed. Each lookahead scans ahead for one requirement, then returns to the start so the next can do the same.

^(?=.*[A-Z])(?=.*\d).{8,}$

"asserts: somewhere ahead there is an uppercase letter,
 somewhere ahead there is a digit, and the whole thing
 is at least 8 characters"

Negative lookahead is the opposite: match here only if something does not come next. The pattern \bcat\b(?! food) matches the word cat unless it is followed by the word food, so cat matches but cat food does not.

Lookbehind: assert what precedes

Lookbehind checks the text just before the current position. It is the clean way to grab a value that follows a marker without capturing the marker itself.

(?<=\$)\d+(?:\.\d{2})?     matches 19.99 in "$19.99", but not the $
(?<!\$)\b\d+               matches a number that is NOT preceded by $

Compare the first with a capturing group. You could write \$(\d+) and read group 1, but then the dollar sign is part of the overall match. With lookbehind, the match is just the number, which is often exactly what a find-and-replace or an extraction needs.

A classic: grouping digits with commas

One well-known recipe uses lookahead to insert thousands separators. The pattern finds every position that has a multiple of three digits remaining to its right, and a replacement inserts a comma there.

pattern:  \B(?=(\d{3})+(?!\d))
replace:  ,

1234567  ->  1,234,567

Stacking conditions: a full password rule

Because each lookahead returns to the same starting position, you can stack as many as you need, and their order does not matter. A common brief, at least one lowercase letter, one uppercase, one digit, and one symbol, with a minimum length, reads as four independent assertions followed by the length check.

^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\w\s]).{8,}$

"Passw0rd!"  matches
"password"   fails (no uppercase, digit, or symbol)

The [^\w\s] class means a character that is neither a word
character nor whitespace, i.e. a symbol or punctuation mark.

Capturing inside a lookahead

A lookahead consumes nothing, but a capturing group placed inside one still records what it saw. That is the standard trick for capturing overlapping matches, which a normal left-to-right scan cannot do because each match starts where the last one ended.

text:     abcde
pattern:  (?=(\w\w\w))

At each position the lookahead matches nothing, but group 1
captures the next three letters: abc, bcd, cde. A plain \w\w\w
would only find abc, then resume at d.

Gotchas and limits

  • JavaScript lookbehind can be variable length. Unlike some older engines, (?<=\$\s*) is legal, so a marker of unknown width still works. That freedom is convenient but can be slow on pathological input, so keep the inner pattern tight.
  • A lookaround does not move the cursor. After (?=foo) succeeds, the next token still starts at the f. If you meant to also match foo, you have to spell it out again or use a normal group instead of a lookahead.
  • Negative lookarounds never capture. There is no text to keep from an assertion that succeeds by not matching, so (?!...) and (?<!...) cannot hand you a value; only positive forms with an inner group can.
  • Anchor a stacked-lookahead pattern. The password example needs the leading ^ so each (?=.*...) scans from the string start; without an anchor and the g flag, the engine can retry from later positions and give confusing partial matches.

See the match boundaries

Lookarounds are far easier to understand when you can see precisely which characters end up highlighted. The Regex Tester shows the exact match in your browser as you type, so the zero-width nature of an assertion becomes obvious. MDN documents the four forms in its reference on assertions.

Test a lookaround nowTest a regular expression against your text in real time. See every match highlighted, with capture groups and flags. Runs entirely in your browser.

Sources

Related articles