Regex Lookahead vs Lookbehind: 5 Real Examples (2026)
Regex lookahead vs lookbehind, explained with five production patterns: password rules, thousands separators, price parsing, and camelCase splits.
Regex Lookahead vs Lookbehind: 5 Real Examples
Lookahead and lookbehind are the two regex features people reach for and then immediately misuse. The confusion is reasonable: both check for a pattern without adding it to the match, and the syntax is dense. This guide pins down regex lookahead vs lookbehind with five patterns pulled from real production code — password rules, number formatting, price parsing, and more — so you can tell which one you actually need.
TL;DR
- Lookahead
(?=...)checks the text to the right; lookbehind(?<=...)checks the text to the left. - Both are zero-width: they match but consume nothing, so the asserted text stays out of your result.
- Negative forms
(?!...)and(?<!...)succeed when the pattern is absent. - JavaScript allows variable-length lookbehind since ES2018; Python's
restill needs fixed width. - Lookbehind matches backward, which changes how capturing groups behave inside it.
How do regex lookahead and lookbehind work?
Lookarounds are collectively called zero-width assertions. They answer a yes/no question at the current cursor position — "is this pattern here?" — and then hand the cursor back unchanged. Nothing they match becomes part of the result string. That is the whole trick, and once it clicks, both directions follow.
What is a lookahead in regex?
A lookahead asserts what comes after the current position. Per MDN, a lookahead assertion "attempts to match the subsequent input with the given pattern, but it does not consume any of the input." Positive lookahead (?=pattern) succeeds when the pattern matches ahead; negative lookahead (?!pattern) succeeds when it does not.
// "foo" only when followed by a digit
/foo(?=\d)/.exec("foo9"); // ["foo"]
/foo(?=\d)/.exec("foobar"); // null
The match is just "foo" — the digit is checked but never captured.
What is a lookbehind in regex?
A lookbehind asserts what comes before the current position. Positive lookbehind (?<=pattern) requires the pattern immediately to the left; negative lookbehind (?<!pattern) requires its absence.
// digits only when preceded by "$"
/(?<=\$)\d+/.exec("$42"); // ["42"]
/(?<=\$)\d+/.exec("#42"); // null
Lookarounds don't consume characters
This is the property that makes lookarounds useful. Because the cursor doesn't advance, you can stack several assertions at the same position and require all of them. A password check is the classic case: three independent rules, one position, zero consumed characters. We'll build exactly that next.
Five real lookaround examples from production code
These are the patterns that show up in code review, not textbook toys. Paste any of them into the iKit Regex Tester to step through the matches live.
Example 1: Validate a password without splitting the regex (lookahead)
You want at least one lowercase letter, one uppercase, one digit, and a minimum length — but the characters can appear in any order. Stacking positive lookaheads at the start checks every rule at position 0 before .+ consumes anything:
const strong =
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/;
strong.test("Sunset2026"); // true
strong.test("sunset2026"); // false (no uppercase)
Each (?=.*X) scans the whole string for one requirement, then resets. If you generate values with the iKit Password Generator, this is the rule you'd validate them against server-side.
Example 2: Add thousands separators with a lookahead
Formatting 1234567 as 1,234,567 is a one-liner once you frame it as "insert a comma at every position that has a multiple-of-three run of digits ahead of it, but only between digits":
"1234567".replace(/\B(?=(\d{3})+(?!\d))/g, ",");
// "1,234,567"
(?=(\d{3})+(?!\d)) is a positive lookahead containing a negative lookahead. The comma lands at zero-width positions, so no digit is lost.
Example 3: Extract the number after a currency symbol (lookbehind)
MDN's own price example is the cleanest demonstration of a lookbehind earning its keep:
function getPrice(label) {
return /(?<=\$)\d+(?:\.\d*)?/.exec(label)?.[0];
}
getPrice("$10.53"); // "10.53"
getPrice("10.53"); // undefined
The $ gates the match but is excluded from the result, so you get a clean numeric string without a slice or a capture group to unpack.
More production lookbehind and negative-lookahead patterns
The negative forms are where lookarounds replace whole branches of imperative code.
Example 4: Match a word that isn't followed by another (negative lookahead)
Find every .js file that is not an already-minified .min.js:
const re = /\b[\w-]+(?<!\.min)\.js\b/g;
"app.js vendor.min.js ui.js".match(re);
// ["app.js", "ui.js"]
Here a negative lookbehind (?<!\.min) sits right before .js and rejects any name ending in .min. Swap it for a negative lookahead and you can exclude trailing patterns just as easily.
Example 5: Split camelCase without consuming characters
Splitting parseHTMLToDOM into words means cutting at the boundary between a lowercase and an uppercase letter — a position, not a character. A lookbehind/lookahead pair marks that position so split consumes nothing:
"parseHTMLToDOM"
.split(/(?<=[a-z])(?=[A-Z])/);
// ["parse", "HTMLTo", "DOM"]
Because both sides are zero-width, no letter is eaten by the split — every character survives in the output array.
How to combine lookahead and lookbehind in one match
Examples 2 and 5 already nest assertions. The general move is: lookbehind for the left context, the real pattern in the middle, lookahead for the right context. The middle is the only part that ends up in your result, which is exactly why lookarounds beat capturing groups when you only want the meat of the match.
Lookbehind gotchas: backward matching and engine support
Lookbehind has one genuinely surprising behavior and one portability trap.
Why does my lookbehind capture the wrong group?
Inside a lookbehind, JavaScript matches backward. MDN gives this example:
/(?<=([ab]+)([bc]+))$/.exec("abc");
// ['', 'a', 'bc'] — not ['', 'ab', 'c']
Because [bc]+ is matched first (right to left), it greedily grabs "bc", leaving only "a" for [ab]+. As MDN explains, the engine "does not know where to start the match... but it does know where to end (at the current position)," so it works backward. Quantified captures inside a lookbehind therefore favor the leftmost match, and backreferences must appear to the left of the group they reference.
Does JavaScript support variable-length lookbehind?
Yes — and this is a real differentiator. ECMAScript 2018 specified both fixed and variable-length lookbehind, and V8 shipped the full version. So (?<=\d{1,3},) is legal in JavaScript. Browser support is now Baseline "widely available"; the last holdout, Safari, landed lookbehind in 16.4 (March 2023), per caniuse.
Python, PCRE, and the fixed-width lookbehind limit
Other engines are stricter. Python's standard-library re module rejects variable-width lookbehind — the pattern inside (?<=...) must be a fixed length, so (?<=\d{1,3}) raises an error. The third-party regex package on PyPI removes that limit. PCRE historically shared the fixed-width restriction. If you're porting a JavaScript regex to a backend, this is the first thing that breaks — confirm it side by side in our JavaScript vs PCRE vs Python regex guide.
| Engine | Variable lookbehind |
|---|---|
| JavaScript (ES2018+) | Yes |
Python re (stdlib) |
No, fixed width only |
Python regex (PyPI) |
Yes |
| PCRE / PCRE2 | Limited |
When NOT to use a lookaround
Lookarounds are precise, not free. Two situations call for restraint.
Performance: catastrophic backtracking
A quantifier inside a lookahead that overlaps a quantifier outside it can blow up exponentially on long inputs. Keep the pattern inside an assertion as specific as you can — anchor it, bound the quantifier — and test against a worst-case string before deploying.
When a capturing group is simpler
If you're extracting one value and the surrounding context is trivial, a capturing group reads more clearly than a lookbehind. Reach for lookarounds when the context is a condition (must be present, must be absent) rather than something you want to keep. When you do need them, validate the pattern against real samples in the iKit Regex Tester — everything runs in your browser, so test data never leaves your machine.
References
- Lookahead assertion: (?=...), (?!...) - JavaScript | MDN — definition and the non-consuming behavior used in Examples 1, 2, and 4.
- Lookbehind assertion: (?<=...), (?<!...) - JavaScript | MDN — backward-matching semantics, the price-parsing example, and the
abccapture demo. - ECMAScript Language Specification — Assertion — normative grammar for lookahead/lookbehind assertions.
- Lookbehind in JS regular expressions — Can I use — browser support timeline, including Safari 16.4.
- Lookahead and Lookbehind Zero-Length Assertions — cross-engine reference for the zero-width assertion model.
Related on iKit
- Bookmark the 25 regex patterns you'll reuse every week — the cheatsheet that pairs naturally with lookaround syntax.
- See why the same regex fails across JavaScript, PCRE, and Python — fixed-width lookbehind is one of the engine differences covered there.
- Compare strict and loose email regex patterns — many "validate" patterns lean on lookaheads the way the password example does.
- Match a URL with regex and dodge the 3 common edge cases — negative lookaheads help exclude trailing punctuation cleanly.
- Test any regex pattern online in 30 seconds — the fastest way to step through the lookaround examples above.
Related posts
Ignore Whitespace in Git Diff: When It Hides Bugs (2026)
Ignore whitespace in a diff and you cut formatting noise — but the same flag can hide real bugs in Python, YAML, and Makefiles. Here's when to use it.
Side-by-Side vs Unified Diff: How to Compare Text (2026)
Side-by-side and unified diff show the same edits two ways. Learn how each format reads, what the @@ hunk header means, and when to pick which.
Regex Capture Groups Explained: $1, $&, and $<name> (2026)
Understand regex capture groups, backreferences, and the $1, $&, and $<name> replacement tokens — with copy-paste JavaScript examples that actually work.