(?=…) and (?!…) — assert what follows, consume nothing¶
One line: A lookahead succeeds if its sub-pattern would match at the current position and then leaves the position where it was — positive (?=…), negative (?!…) — in every engine here except Go and Rust's regex, which have no lookaround at all; and the negative form's most common use is a bug, because "not followed by X" is true one character earlier.
Level: reference · one construct, eight engines
What it means¶
Zero width: the assertion tests, it does not take. \d+(?= EUR) matches 25 in 25 EUR and the match ends after the 5; the EUR is checked and left alone. That is what makes lookahead useful in a replacement or an extraction — the context is verified without becoming part of the result.
A lookahead can contain groups, and the groups capture. (?=(a+))\1 is a lookahead that captures a+ and a backreference that then consumes it — which is an atomic group built out of two other features, and the way JavaScript gets one.
Measured¶
Verified output of kw_lookahead_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
construct python perl node ruby java go rust fancy
positive 25 25 25 25 25 - - 25
negative, the trap 2 2 2 2 2 - - 2
negative, fixed no no no no no - - no
lookahead can capture aaa aaa aaa aaa aaa - - aaa
at the start ab1 ab1 ab1 ab1 ab1 - - ab1
Reading the table¶
- Row 1.
\d+(?= EUR)gives25in six engines. Go and Rust'sregexrefuse the construct entirely. - Row 2, the trap.
\d+(?! EUR)on25 EURmatches2in all six. The intent was "a number not followed by EUR"; the engine's reading is "a run of digits after which the next text is notEUR", and2— followed by5— satisfies that. Backtracking found the answer that makes the negative assertion true. Nothing was wrong with the engine. - Row 3, the fix.
\b\d+\b(?! EUR)— a boundary so the digits cannot stop early — answersno, which is the correct answer for this subject. When the engine has no lookaround shows a replacement that is more correct than this lookahead. - Row 4. The capturing lookahead,
(?=(a+))\1, matchesaaa: the lookahead captured threeas, the backreference consumed them. - Row 5, lookahead at the start.
(?=.*\d)\w+is the password-rule idiom — "must contain a digit" checked before the real match starts — and it works everywhere lookahead exists.
The trap¶
Row 2. Negative lookahead after a quantifier is the single most-reported "my regex is wrong" shape in this library's experience, and it is usually the pattern being right and the author's model being wrong. The assertion is evaluated at one position; the quantifier is free to choose that position. Anchor the quantifier's end with a boundary or a possessive/atomic form, or put the assertion before the quantifier where its meaning is fixed.
Where it is measured in depth¶
- Lookahead and lookbehind — the lesson, with
['25', '90']and the width rules. - When the engine has no lookaround — what to write in Go and Rust.
- Atomic groups and possessive quantifiers — the
(?=(a+))\1emulation.
See also¶
- Lookbehind
(?<=…)· Atomic group · Conditional —(?(?=…)yes|no). - Validating input.