Lesson 41 of 80
Assertions that check what's next to a match without consuming it — how you write a password rule that needs three separate conditions to all be true at once.
15 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
Most of a regex consumes characters — it matches them and moves the cursor past them. Lookahead and lookbehind are different: they peek at nearby text to decide whether a match is allowed, then throw that peek away without consuming anything.
There are four of these, and the names describe exactly what they check:
(?=...) — positive lookahead: what follows must match.(?!...) — negative lookahead: what follows must NOT match.(?<=...) — positive lookbehind: what precedes must match.(?<!...) — negative lookbehind: what precedes must NOT match.None of the four add characters to your match. They're a condition attached to a position in the string, not a piece of the result.
A common rule: password must be 8+ characters, contain a lowercase letter, an uppercase letter, and a digit — in any order. Without lookahead, you'd need to check three separate regexes and combine the results in code. With lookahead, one pattern does it:
function isValidPassword(password) {
const rule = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/;
return rule.test(password);
}
isValidPassword("password1"); // false — no uppercase
isValidPassword("Password1"); // true
isValidPassword("Pass1"); // false — only 5 characters
Read it left to right: at the very start of the string (^), first check "somewhere ahead there's a lowercase letter" ((?=.*[a-z])), without moving the cursor. Then, still at the start, check "somewhere ahead there's an uppercase letter." Then a digit. All three checks happen from the same starting position because none of them consume anything. Only after all three pass does .{8,}$ actually consume 8+ characters to the end.
Lookbehind flips the direction. Say you're extracting a price but don't want the currency symbol:
const text = "Total: $42.50";
const amount = text.match(/(?<=\$)\d+\.\d+/)[0];
// "42.50" — the $ had to be there to match, but it's not part of the result
(?<=\$) asserts "a dollar sign comes right before this position" without including the $ in what gets returned. Compare that to a capture group approach, which would include the $ in the full match and force you to pull the group out separately.
(?<=\d+), but don't assume every regex engine you've used elsewhere does — some still require a fixed width.This isn't a separate feature bolted onto regex — it's the same pattern-matching engine used for .test() and .match() everywhere else in the language, just with assertions that don't consume input. If you're comfortable with character classes and quantifiers already, lookaround is a small extension of that same mental model: a condition on a position, not a new kind of matching.