Skip to content

(?<=…) and (?<!…) — assert what precedes, and how wide that may be

One line: A lookbehind succeeds if its sub-pattern matches ending at the current position, consuming nothing; every engine here except Go and Rust's regex has it, and they split four ways on how wide it may be — fixed only (Python, fancy-regex), a bounded repeat allowed (Ruby: no; Perl and Java: yes), and unbounded (JavaScript, Java).

Level: reference · one construct, eight engines

What it means

Lookbehind is harder to implement than lookahead, because the engine has to know how far back to look. Each engine drew its own line:

Engine (?<=a) fixed (?<=a|bc) two widths (?<=a{1,3}) bounded (?<=a+) unbounded
Python re yes no no no
Ruby yes yes no no
Perl 5.30+ yes yes yes (≤ 255) no
PCRE2 (so ABAP) yes yes yes (≤ 255) no
Java yes yes yes yes
JavaScript (ES2018) yes yes yes yes
fancy-regex yes no no no
Go, Rust regex — — — —

Measured

Verified output of kw_lookbehind_sh.sh — regenerated by tools/run_examples.py, never hand-typed.

construct       python  perl  node  ruby  java  go  rust  fancy
fixed width     25      25    25    25    25    -   -     25
negative        30      30    30    30    30    -   -     30
two widths      -       d     d     d     d     -   -     d
bounded repeat  -       b     b     -     b     -   -     -
unbounded       -       -     b     -     b     -   -     -

Reading the table

  • Rows 1–2, fixed width. (?<=\$)\d+ finds 25 after the dollar sign; (?<!\$)\b\d+ finds the 30 that has none. Six engines agree; Go and Rust refuse.
  • Row 3, two widths. (?<=a|bc)d — alternatives one and two characters long. Perl, JavaScript, Ruby and Java accept; Python and fancy-regex refuse, because their rule is one fixed width for the whole assertion. The workaround in Python is (?:(?<=a)|(?<=bc))d: two lookbehinds, each fixed.
  • Row 4, a bounded repeat. (?<=a{1,3})b: Perl, JavaScript and Java accept; Ruby joins Python and fancy-regex in refusing. Ruby's rule is "alternatives may differ, a quantifier may not".
  • Row 5, unbounded. (?<=a+)b: JavaScript and Java only. Perl's limit is 255 characters and an unbounded repeat exceeds it by definition.

The trap

Writing the lookbehind in the engine with the widest support and porting it to the one with the narrowest. A (?<=\d{1,3}) that worked in Java is a compile error in Python and Ruby; a (?<=https?://) — two widths — works in five engines and is refused by Python, which is exactly the engine it is most likely to be pasted into next. The error messages say "look-behind requires fixed-width pattern" and are at least loud.

Where lookbehind is missing or too narrow, capture the context instead and discard it: (\$)(\d+) and take group 2. The no-lookaround lesson makes the case that this is often the clearer pattern anyway.

Where it is measured in depth

See also