Lookarounds let you match a spot based on what surrounds it — without swallowing those surroundings. Once that clicks, half the "impossible" regex problems get easy.
Most people learn regex by matching things — a date, an email, a word. Lookarounds flip the question. Instead of "match this," they ask "is this thing next to what I'm about to match?" — and then they don't touch it. That's the whole trick, and it's also why they confuse everyone at first: a lookaround finds a position, not text. Get comfortable with that one idea and a pile of problems that felt impossible — "match a number but only if it's followed by a currency symbol," "split on a comma that isn't inside quotes" — turn into two-minute jobs.
Here's how the four forms actually work, and the handful of places they bite.
1. Zero-width is the whole point
A normal regex token consumes characters. When \d+ matches 42, the cursor moves past both digits and they land in your result. A lookaround consumes nothing. It's an assertion — a true/false check at the current position — and the cursor stays put. That's why people call lookarounds "zero-width": they have a location but no width.
The practical payoff is that you can test the surroundings without stealing them. If you want the 100 in 100px but not the px, a lookahead lets you require the px is there while leaving it out of the match. You checked for it without grabbing it.
2. The four forms, plainly
There are exactly four, split on two axes — direction (ahead/behind) and polarity (positive/negative):
- Positive lookahead
(?=...)— "what follows must match this."\d+(?=px)matches digits only whenpxcomes next. - Negative lookahead
(?!...)— "what follows must not match this."\d+(?!px)matches digits not followed bypx. - Positive lookbehind
(?<=...)— "what precedes must match this."(?<=\$)\d+matches digits only when a$sits just before them. - Negative lookbehind
(?<!...)— "what precedes must not match this."(?<!\$)\d+matches digits with no dollar sign in front.
The mnemonic that sticks: the = is "yes," the ! is "no," and the < means "look left." Everything without a < looks right. That's the entire grammar.
3. Where lookbehind gets you: variable length
This is the number-one surprise. Many engines require the lookbehind to be fixed-width — the thing you're looking back at must be a known, constant number of characters. (?<=cat) is fine (always three). (?<=cats?) or (?<=\d+) often isn't, because the engine can't cheaply walk backwards over an unknown span.
Support is genuinely inconsistent, so this is a place to check your target rather than assume. .NET allows fully variable-length lookbehind. JavaScript (since ES2018) allows it too. Java and PCRE historically restrict it to fixed-length (PCRE allows alternatives of different fixed lengths). Python's old re module wants fixed-width, while the third-party regex module lifts that. If a pattern works in one language and throws in another, a variable-length lookbehind is the first thing to suspect.
"The lookbehind assertion ... in Python's
— Python documentation,remodule, the contained pattern must only match strings of some fixed length."re— Regular expression operations
4. The overlap trick negative lookahead unlocks
Because lookarounds don't consume, you can stack several at the same position — which is how you express "matches all of these conditions at once." A password check is the canonical example: ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$. Each (?=.*X) scans ahead from the start for one requirement, then rewinds to the beginning because it consumed nothing. Three independent "must contain" tests, all anchored at the same spot, then a final .{8,} that actually does the matching.
Negative lookahead pulls the opposite duty — excluding a case mid-pattern. The famous "match a quote-safe comma" and "match a word that isn't a keyword" problems both lean on (?!...) to carve an exception out of an otherwise greedy pattern. It's the closest regex gets to saying "everything except."
5. When a lookaround is the wrong tool
Lookarounds are precise, but they're not free — and they're not always the clear answer. If you only need the surrounding text gone from the result, a capture group is often simpler: match the whole thing, then keep group 1. \$(\d+) with a grab of group 1 does the same job as (?<=\$)\d+, reads more obviously to the next person, and dodges the fixed-width lookbehind trap entirely.
Reach for a lookaround when you genuinely can't consume the surroundings — because you're doing a replace that must leave them intact, or you're chaining multiple conditions at one position, or a split has to happen between characters without eating either side. Those are the cases where nothing else is as clean. For "just pull out the number," a plain capture group usually wins on readability.
The one-line takeaway
A lookaround asserts something about the neighborhood without joining it. Positive/negative sets the polarity, the < sets the direction, and "zero-width" is the property that makes stacking and exclusions possible. Watch for variable-length lookbehind across engines, and don't reach for a lookaround when a capture group says the same thing more plainly.
The fastest way to internalize any of this is to watch a pattern light up character by character — our regex tester highlights matches live as you type, so you can see exactly where a lookahead grabs and where it lets go. If you just need the syntax at a glance, the regex cheatsheet lays out all four lookaround forms next to the rest of the metacharacters, and when you're refining a pattern that an LLM handed you, regex for LLM output is built for exactly that cleanup loop.
← All articles