Lookarounds are zero-width assertions that test what is ahead or behind the current position without consuming characters. Positive lookahead, written (?=...), asserts that the upcoming text matches the subpattern and then returns the position to where it was, so the surrounding pattern can continue matching. The pattern \d+(?=px) captures the digits in 42px but rejects 42em because the lookahead requires the digits to be followed by px. Negative lookahead, written (?!...), asserts that the upcoming text does NOT match the subpattern, so foo(?!bar) matches foo only when it is not immediately followed by bar. Lookaheads are evaluated at the current position, and the engine does not advance past them, which is why they are perfect for prefix and suffix constraints.
Lookbehinds are the mirror image: they inspect the text before the current position. Positive lookbehind, written (?<=...), asserts that what precedes the position matches the subpattern; negative lookbehind, written (?<!...), asserts the opposite. A practical use is matching a price without including the currency symbol: (?<=\$)\d+(\.\d{2})? matches 19.99 in $19.99 but only when a dollar sign precedes it. Lookbehinds were historically limited to fixed-width subpatterns, which constrained their usefulness, but modern engines have relaxed this. JavaScript supports lookbehinds since ES2018, Python 3.11+ allows variable-width lookbehinds, and PCRE2 supports them fully. When you need a feature that is not yet universal, prefer lookahead, which is widely supported across all flavors.
Lookarounds become particularly powerful when you need to enforce several independent rules in a single pattern. Password validation is the classic example: ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z\d!@#\(%^&*]{8,}\) uses three positive lookaheads to require at least one lowercase letter, one uppercase letter, and one digit, while the final consuming class with the length quantifier ensures the entire input is at least eight characters and made of allowed symbols. The lookaheads do not consume characters, so each one independently scans the string from the start. This composition is much harder to express with capturing groups or alternation. Lookarounds also make it easy to convert re.match semantics to re.search semantics in Python, because the leading ^ can be replaced with a lookahead that does not consume the start of input.