What lookarounds are, in plain words
Most regex matches against a piece of text and consumes the part it matched. Lookarounds are different. They look around the current position to confirm a condition, but they do not consume any text.
Think of them as conditions you can attach to a match. โMatch a number, but only if it is preceded by a dollar signโ. โMatch a word, but only if it is not followed by a question markโ. The dollar sign and the question mark stay where they are, the match starts and ends around them.
There are four variants, two looking forward and two looking backward, each with a positive and a negative version.
Quick reference
| Syntax | Name | Use |
|---|---|---|
(?=...) | Positive lookahead | Match if followed by pattern |
(?!...) | Negative lookahead | Match if not followed by pattern |
(?<=...) | Positive lookbehind | Match if preceded by pattern |
(?<!...) | Negative lookbehind | Match if not preceded by pattern |
| Variable width | Lookbehind length | Modern engines only (JS, .NET, PCRE2) |
The four lookaround patterns
Positive lookahead (?=...): matches if what follows fits the pattern.
"42 dollars".match(/\d+(?= dollars)/); // ["42"]
"42 euros".match(/\d+(?= dollars)/); // null
The match is 42. The space and dollars are not in the result, they are just confirmation.
Negative lookahead (?!...): matches if what follows does NOT fit the pattern.
"foo bar".match(/foo(?! bar)/); // null
"foo baz".match(/foo(?! bar)/); // ["foo"]
Positive lookbehind (?<=...): matches if what precedes fits the pattern.
"price: 42".match(/(?<=price: )\d+/); // ["42"]
"42".match(/(?<=price: )\d+/); // null
Negative lookbehind (?<!...): matches if what precedes does NOT fit the pattern.
"abc 42".match(/(?<!\d)\d+/); // ["42"]
"3 42".match(/(?<!\d)\d+/); // ["3"] (the 42 fails the lookbehind)
When lookarounds save you
Three patterns come up constantly:
Match a value preceded by a label:
"name: Maria, age: 32".match(/(?<=age: )\d+/); // ["32"]
Without lookbehind, you would capture age: 32 and then strip the label. Lookbehind makes the match clean.
Match passwords with multiple constraints:
const password = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{12,}$/;
Each lookahead checks a different rule (one lowercase, one uppercase, one digit, one symbol). Without lookaheads, you would write four separate regexes and combine them in code.
Replace something only in a context:
"a, b, c, d".replace(/(?<=, )\b\w/g, m => m.toUpperCase());
// "a, B, C, D"
Capitalise letters that come after , . Without lookbehind, the comma and space would become part of the match and the replacement would have to add them back.
Variable width lookbehind, the modern feature
Until recently, most regex engines required lookbehind to be a fixed length. You could write (?<=abc)X but not (?<=a.*c)X. The reason is engine internals: the engine needed to know how far back to look.
In 2026, the picture is mostly fixed:
- JavaScript (V8, JavaScriptCore, SpiderMonkey): variable width lookbehind supported since 2018. Use freely.
- Python: only fixed width. Use the external
regexpackage for variable width. - PCRE2: variable width since version 10.
- .NET: variable width supported.
- Java: variable width with finite alternation since Java 13, fully variable since Java 17.
- Go (
regexp/RE2): no lookbehind at all. RE2 forbids backreferences and lookarounds to guarantee linear time. - Rust (
regex): no lookbehind. Same lineage as RE2. - Ripgrep /
rg: no lookbehind by default. Pass the PCRE2 flag to enable it.
If you need lookbehind in a language without support, the workaround is a capture group plus a substitute that drops the prefix. It is uglier but portable.
// JS variable width lookbehind
"123abc456def".match(/(?<=[a-z]+)\d+/g);
// ["456"]
If you need to test a regex without writing code, paste it into the regex tester on AldeaCode. It runs the pattern against your text in your browser, no upload.
The pitfalls
Catastrophic backtracking: lookarounds inside greedy patterns can cause the engine to try exponentially many paths. The fix is to anchor lookarounds to specific characters or use possessive quantifiers (where supported).
Atomic confusion: a lookbehind matches the position, not the captured text. If you try to extract the lookbehind content into a capture, it will not work. Use a real capture group for the prefix and let the rest of the regex do its thing.
Engine surprises: a regex that works in JavaScript might fail in Python or Go. Always test in the actual engine you will run on. Do not write regex on Stack Overflow and assume it ports cleanly.
A practical workflow
When you reach for a lookaround, ask:
- Am I trying to match something only in a specific context? Lookahead or lookbehind.
- Am I trying to exclude something based on context? Negative lookahead or lookbehind.
- Does my engine support variable width if I need it? Check before writing.
- Will my regex run on hostile input? Watch for catastrophic backtracking.
The regex tester, the find and replace for testing the pattern against actual text, and the remove lines containing for filtering with simpler patterns all run in your browser. Lookarounds are one of those features that turn ten lines of code into one regex. Used carefully, they are precise. Used carelessly, they are the reason your CI takes 30 minutes.