iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Look-ahead / behind

Lookahead ((?=...) / (?!...)) and lookbehind ((?<=...) / (?<!...)) are zero-width assertions: they match positions, not characters. Used to add context without consuming.

Practical lookaround recipes

EXAMPLE
// 1) Positive lookahead — match X only if followed by Y
/foo(?=bar)/.test('foobar')      // true — matches 'foo'
/foo(?=bar)/.exec('foobar')[0]   // 'foo' (the 'bar' is NOT consumed)

// 2) Negative lookahead — match X only if NOT followed by Y
/foo(?!bar)/.test('foobar')      // false
/foo(?!bar)/.test('foobaz')      // true

// 3) Positive lookbehind — match X only if preceded by Y
/(?<=USD)\d+/.exec('USD42')[0]   // '42'

// 4) Negative lookbehind
/(?<!\\)n/.test('foo\\n')        // false — the `n` IS preceded by \
/(?<!\\)n/.test('foon')          // true

// 5) Real recipes

// 5a) Password strength — has digit + lowercase + uppercase + length >= 8
const strong = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}$/;
strong.test('Hunter2!');         // true
strong.test('hunter');           // false

// 5b) Match a word NOT followed by a comma
/\bfoo\b(?!,)/g

// 5c) Extract numbers that are NOT IDs (i.e. not preceded by 'id=')
const s = 'id=42 amount=99';
[...s.matchAll(/(?<!id=)\b\d+\b/g)].map(m => m[0]);    // ['99']

// 5d) Quoted-string contents (greedy avoidance)
const q = `name="Ada", role="admin"`;
[...q.matchAll(/"([^"]*)"/g)].map(m => m[1]);          // ['Ada', 'admin']

// 5e) Split on commas NOT inside quotes (CSV-lite)
'a,"b,c",d'.split(/,(?=(?:[^"]*"[^"]*")*[^"]*$)/);   // ['a', '"b,c"', 'd']

// 6) Replace using lookaround
// Add a thousands separator (no consume of digits before)
'1234567'.replace(/\B(?=(\d{3})+(?!\d))/g, ',');     // '1,234,567'

// 7) Lookbehind support
//    JavaScript: lookbehind needs a modern engine (V8 in Node 10+, Safari 16.4+).
//    PCRE / Python / Java / .NET: supported, variable-length too.
//    POSIX (basic grep / awk): NO lookaround — use ERE / Perl flavour.

// 8) Common pitfalls
//   • Lookarounds are zero-width — they don't appear in the match string
//   • Variable-length lookbehind support varies; recent JS engines DO allow it
//   • Catastrophic backtracking — combine lookahead with greedy quantifiers carefully
//
// Tools:
//   • regex101.com — visualise + benchmark + step through
//   • regexr.com    — quick reference
//   • RE2 (Go, ripgrep) — no lookaround; linear-time guarantee

Why it matters

Lookarounds are the regex version of “asserting context.” They turn ambiguous matches into precise ones — but if you can express the same thing with capture groups + post-filter, the regex is usually easier to read.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// Lookahead is widely supported; lookbehind is in modern engines (ECMAScript 2018+).
foo(?=bar)     // foo followed by bar (consume only foo)
foo(?!bar)     // foo NOT followed by bar
(?<=foo)bar    // bar preceded by foo
(?<!foo)bar    // bar NOT preceded by foo
Try it Yourself »

Exercise

Negative lookahead syntax.

/foo(? bar)/

Test yourself

Q1. Lookahead consumes…
Q2. Negative lookahead is written…
Q3. Lookbehind is supported in modern…

Discussion

Loading…