Lookaround Is Just an Assertion About Position
Most regex confusion around lookahead and lookbehind comes from one misconception — people expect them to match text. They do not. Lookaround is a zero-width assertion: it checks whether a condition is true at the current position and then hands control back without consuming a single character. The cursor stays exactly where it was. Once that clicks, every pattern below stops being mysterious. You can verify each of these live by pasting them into our Regex Tester and watching what gets highlighted versus what merely gets checked.
The ECMAScript specification defines these constructs in the Assertion production of the regular-expression grammar (ECMA-262, section 22.2.1, "Patterns"). Lookahead has been in JavaScript since the beginning; lookbehind and named groups were standardized in ES2018. That history matters because lookbehind is the one feature you still cannot assume exists in every old runtime, a point we return to at the end.
The Four Forms at a Glance
There are exactly four lookaround operators, and they pair neatly along two axes — direction and polarity.
- Positive lookahead
(?=…)— "what follows here matches X". - Negative lookahead
(?!…)— "what follows here does not match X". - Positive lookbehind
(?<=…)— "what precedes here matches X". - Negative lookbehind
(?<!…)— "what precedes here does not match X".
The lookbehind syntax simply inserts < after the question mark. A useful memory hook — the angle bracket points backward, toward the text already consumed. Everything else is identical to lookahead.
Lookahead: Matching a Thing by What Comes After It
Suppose you want the numeric amount in "50 dollars" but not the word. A naive \d+ works, but the moment you also need to exclude "50 euros" the lookahead earns its keep. The pattern \d+(?= dollars) matches "50" only when the literal " dollars" follows, yet that word is never part of the match. Paste it into the Regex Tester against "I paid 50 dollars and 30 euros" and you will see exactly one highlight over "50".
Negative lookahead is the inverse and is where lookahead becomes genuinely powerful. Imagine extracting every number that is not a version suffix. The pattern \d+(?!\.\d) matches digits not immediately followed by a dot-and-digit. This kind of "match unless" logic is awkward to express any other way, because regex has no native "but not when" operator outside of lookaround.
The Classic: Password Rules in One Pattern
The single most common real-world use of lookahead is compound validation. A password policy might demand at least one lowercase letter, one uppercase letter, one digit, and a minimum length of eight. Each requirement is an independent anchored lookahead stacked at the start:
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$
Read it as a checklist. The ^ anchors to the start, then each (?=.*X) peeks ahead across the whole string to confirm one class exists — without moving the cursor, so the next assertion starts again from position zero. Only after all three pass does .{8,}$ actually consume the characters and enforce the length. Remove any one lookahead in the Regex Tester and watch which inputs start passing; it is the fastest way to feel how each assertion contributes independently.
Lookbehind: Matching a Thing by What Comes Before It
Lookbehind answers the mirror-image question. To grab the digits of a price written "$50" without capturing the currency symbol, use (?<=\$)\d+. The assertion confirms a dollar sign sits immediately to the left, and the match contains only "50". This is cleaner than a capture group because there is no group to dereference later — the match itself is already exactly what you wanted.
Negative lookbehind is just as practical. To find every occurrence of "cat" that is not preceded by "wild", write (?<!wild)cat. Against "the cat near the wildcat" it matches the first "cat" and skips the one inside "wildcat". Combined with word boundaries, negative lookbehind lets you carve out precise exceptions that would otherwise demand post-filtering in application code.
Variable-Length Lookbehind: A JavaScript Superpower
Here is a detail that surprises developers coming from other languages. Java and PCRE historically restricted lookbehind to fixed or bounded length. JavaScript's ES2018 lookbehind has no such restriction — the assertion inside (?<=…) may match a variable number of characters. That means (?<=\$\d+\.)\d+ validly looks behind across an arbitrary run of digits. This freedom comes from how V8 and SpiderMonkey execute lookbehind by matching the inner pattern right-to-left, a behavior described in the TC39 lookbehind proposal that became part of ECMA-262. If you have ever been told "lookbehind must be fixed width", that rule simply does not apply to modern JavaScript.

Combining Lookahead and Lookbehind: Surgical Extraction
The two directions compose. To pull a value sitting between two delimiters — say the token inside key="value" — you can flank the match with both assertions: (?<=key=")[^"]*(?="). The lookbehind requires key=" on the left, the lookahead requires a closing quote on the right, and [^"]* in the middle is the only consumed text. The result is the bare value, no trimming required.
This pattern of "anchored on both sides, consume the middle" is one of the most reusable lookaround idioms. It shows up when scraping attribute values, slicing fields out of log lines, and isolating fragments inside structured strings. When the surrounding structure gets genuinely nested or recursive, though, stop — regex is the wrong tool, and a real parser like our JSON Path Query tool is the right one for navigating tree-shaped data.
Inserting Thousands Separators Without Touching the Number
A favorite demonstration of lookaround in substitution is formatting a long integer with commas. The pattern \B(?=(\d{3})+(?!\d)) matches the empty positions where a comma belongs, then you replace each match with a comma. It works because the lookahead asserts that a multiple-of-three run of digits follows, the (?!\d) stops the run at the right edge, and \B avoids matching at the very start. Against "1234567" the substitution yields "1,234,567". Open the Regex Tester's substitution panel, set the replacement to a comma, and step through the matches to see the zero-width positions light up one by one — it is the clearest possible illustration that lookaround matches places, not characters.
Masking and Redaction with Negative Assertions
Privacy-preserving transforms lean heavily on lookaround. To mask all but the last four digits of a card-like number, you match each digit that has at least four more digits after it: \d(?=\d{4}) with the global flag, replacing each match with an asterisk. "4111111111111234" becomes "************1234". No capture groups, no arithmetic on string lengths — the assertion does the counting. The same approach masks emails, phone numbers, and identifiers, all entirely in the browser so the sensitive value never leaves your machine. That client-side guarantee is exactly why a tool you can run locally beats pasting secrets into a remote service.
The reason this works is worth dwelling on, because it is the clearest single demonstration of zero-width thinking. The regex engine sits on a digit, asks the lookahead "are there four more digits to my right?", and only matches the current character when the answer is yes. As the engine sweeps left to right it naturally stops four positions from the end, because at that point no run of four trailing digits remains to assert. You did not have to compute length - 4 anywhere; the assertion derived the boundary for you. Swap \d{4} for \d{2} in the Regex Tester and the unmasked tail shrinks to two characters — the pattern reasons about position so you do not have to.
Why "Validate, Don't Capture" Reads Better
A recurring lesson across all these examples is that lookaround often replaces a capture group plus a slice. Compare two ways of pulling the value from price=42. With a capture you write price=(\d+) and then reach into group 1. With lookbehind you write (?<=price=)\d+ and the whole match is the value. The second form expresses intent more directly — "the number that follows price=" — and it removes an entire layer of indirection from the calling code. When a pattern is destined for a one-line match or replace rather than a structured extraction, prefer the assertion. Your future self reading the regex will thank you, because the shape of the match mirrors the shape of the answer.
Pitfalls That Trip Everyone Up
- Expecting lookaround to consume. It never does. If your match table shows the highlight is shorter than you expected, that is correct behavior — the asserted text stays outside the match.
- Stacked lookaheads and ordering. Anchored lookaheads like the password example all start from the same position, so their order does not matter for correctness — but a missing
.*inside one will silently make it fail, since the class must be reachable from where the cursor sits. - Capturing inside an assertion. A capture group placed inside a lookahead, such as the
(\d{3})above, still populates a numbered group even though the lookahead is zero-width. That is occasionally useful and occasionally a surprise when your group indices shift. - Old runtimes lack lookbehind. Lookbehind throws a syntax error in engines predating ES2018, including older Safari and many embedded JavaScript environments. Feature-detect with a
try/catcharoundnew RegExp("(?<=a)b")before relying on it in code that ships widely. - Performance with nested lookahead. Lookahead containing greedy quantifiers can still backtrack heavily. Keep the inner pattern as specific as possible, the same discipline that prevents catastrophic backtracking elsewhere.
A Five-Minute Drill You Can Run Now
Theory dissolves the moment you watch these run. Open the Regex Tester and work through this sequence, editing one character at a time. Start with \d+(?= dollars) on a mixed-currency sentence. Switch the lookahead to negative and observe the matches flip. Add a second stacked lookahead to build a password validator from scratch. Convert (?<=\$)\d+ from positive to negative lookbehind and predict the change before you hit run. Finally, paste the thousands-separator substitution and step through its zero-width matches.
By the end of that drill the four operators stop being abstract symbols and become tools you reach for deliberately. Lookahead asks about what is ahead, lookbehind about what is behind, and the polarity flips the question from "must be" to "must not be". Everything else — password rules, masking, delimiter extraction, number formatting — is just those four assertions composed with the ordinary character classes and quantifiers you already know. For more foundational coverage, our complete guide to regex syntax walks through the building blocks that lookaround sits on top of, and pairs naturally with this deeper dive into assertions.
The payoff compounds. Once you can read a stacked-lookahead validator at a glance, the next person's clever pattern stops being a wall of punctuation and starts reading like a sentence — and you will write your own with the quiet confidence of someone who knows exactly where the cursor is standing.