The Developer's Guide to Regex Lookaheads and Lookbehinds
Positive and negative lookarounds explained with practical JavaScript examples — password validation, currency matching, URL slug extraction, and more.
Most regex you write is "match this pattern." Lookarounds are "match this pattern, but only if (or only if not) something else is nearby." They're the tool that takes you from "regex I could have written in 2010" to "regex that actually solves the problem without a second pass in code."
Four flavors exist, all supported in modern JavaScript:
(?=...)— positive lookahead: assert what follows(?!...)— negative lookahead: assert what does not follow(?<=...)— positive lookbehind: assert what precedes(?<!...)— negative lookbehind: assert what does not precede
The critical thing: lookarounds don't consume characters. They're assertions. The regex engine peeks, confirms, and continues from where it was. That's what makes them powerful — and occasionally confusing.
Lookaheads have been in JavaScript regex forever. Lookbehinds (both flavors) were standardized in ES2018 and are supported everywhere modern — Node 10+, Chrome, Firefox, Safari 16.4+. If you're targeting truly ancient environments, test first. For everyone else, they're safe to use.
Positive Lookahead: "Only Match If Followed By..."
The classic use case is password validation. You want a string that contains at least one digit, at least one uppercase letter, at least one special character, and is at least 12 characters long. A single regex can check all of these by stacking positive lookaheads at the start.
Read the lookaheads from left to right:
(?=.*\d)— somewhere ahead there's a digit(?=.*[A-Z])— somewhere ahead there's an uppercase letter(?=.*[!@#$%^&*])— somewhere ahead there's a special character
All three assertions apply to position 0 (the ^ start anchor), so they all check the entire string from the beginning. None of them consume characters, so the [A-Za-z\d!@#$%^&*]{12,}$ part gets to match from position 0 as well.
This is the idiomatic way to do password validation in a single pass. The alternative is three separate .test() calls in JavaScript — more code, same result, slightly less clever.
Negative Lookahead: "Only Match If NOT Followed By..."
Find all prices not preceded by a currency symbol — say, to flag numbers in a document that look like money but lack proper formatting.
Actually, that's a lookbehind problem. Let's flip it: find all version numbers that are not followed by a hyphen suffix like -alpha or -beta — just the clean releases.
The (?!-) at the end says "this version number is only a match if the next character is not a hyphen." The lookahead doesn't consume the following character, so if the next thing is a space or punctuation, the match succeeds without capturing it.
Another classic: matching words that aren't part of a blocklist. You want to find every delete that isn't delete_from_archive:
Positive Lookbehind: "Only Match If Preceded By..."
Now the prices-preceded-by-a-currency example, done right.
The (?<=\$) asserts "a $ is directly behind this position." Notice that $ isn't part of the match — the lookbehind doesn't consume it, so match() returns the number without the symbol. That's often exactly what you want when you're about to parseFloat() the result.
Compare that to capturing the $ and then stripping it in code. The lookbehind version is less code and harder to get wrong.
Negative Lookbehind: "Only Match If NOT Preceded By..."
Use case: extracting URL slugs from paths, but only for articles (not user profiles).
The (?<!\/users) says "this slash is only the start of a match if /users is not directly behind it." Paths starting with /users/ are excluded; everything else passes.
This pattern shows up constantly in routing, logging filters, and content extraction. Doing it without lookbehind means matching everything and then filtering in code — more lines, same result, and easier to leak edge cases.
Combining Them
Lookaheads and lookbehinds compose cleanly. You can chain them at any position in a pattern. A contrived-but-useful example: extract every number that's between a $ and USD, without capturing either.
The (?<=\$) and (?= USD) bookend the actual match. Only numbers between those two markers are captured, and neither marker is in the result.
Performance Reality Check
Lookarounds don't have a big performance cost in practice — the regex engine is usually smart about them — but a few things do matter:
- Variable-length lookbehinds used to be unsupported in many engines. V8 supports them now. Older JS runtimes (IE, old Node) do not. If you're shipping to a known-modern target, fine.
- Catastrophic backtracking can still happen if the inside of a lookaround is itself a nasty pattern.
(?=(a+)+b)is a bad idea for the same reason(a+)+bis a bad idea outside one. - Unicode property escapes (
\p{Letter}) inside lookarounds need theuflag. If you're matching international text, add/u.
When to Reach for Them
Lookarounds are worth it when the alternative is:
- A two-step pipeline (regex then filter) that's actually one logical operation.
- A capture group just to throw away the captured character.
- Multiple
.test()calls where a single assertion chain would work.
They're not worth it when the pattern becomes unreadable. A lookaround buried five levels deep in a 200-character regex is a maintenance hazard. If your teammates can't read it six weeks later, simpler code with a comment beats clever regex.
Test lookarounds with real input before you ship them. The assertion-based nature makes dry-running in your head unreliable — you think you know what matches, then a trailing newline or an unescaped dot proves otherwise.
The Bottom Line
Lookaheads let you peek at what follows without consuming it. Lookbehinds do the same for what precedes. Negative versions invert both. You need them when a match depends on context but the context shouldn't be part of the result.
Stack them for multi-rule validation. Bookend them for content extraction. Test them in a sandbox before you commit.
Try these tools
More articles
Choosing Between CODE128, EAN-13, UPC, and ITF-14 Barcodes
A decision tree for picking the right barcode format — what each encodes, length limits, checkdigit rules, and when to pick CODE128 versus retail-specific formats.
How to Merge PDFs in Your Browser Without Uploading Them Anywhere
Why client-side PDF merging matters for privacy, how pdf-lib works under the hood, and the limitations you need to know before trusting a browser tool.
JSON vs YAML for Config Files: Pick the Right Tool, Skip the Pain
A practical guide to choosing between JSON and YAML for configuration files, with concrete gotchas and recommendations for Kubernetes, APIs, and more.