iKit
Technical · 10 min read ·

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 (2026)

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 re still 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

Related on iKit

Related posts