· tips · 5 min read
Regex Lookbehinds in JavaScript: When and Why They Matter
A practical guide to JavaScript lookbehind assertions: what they are, when to use them, real-world examples, browser/Node compatibility notes, and fallbacks for older environments.

What you’ll be able to do
Use lookbehind assertions in JavaScript to write clearer, safer regexes that match what you want without including the context in the result. We’ll cover syntax, real-world examples, browser and Node compatibility traps, performance caveats, and practical fallbacks for older environments. Read this once and you’ll stop writing brittle capture-group hacks for simple tasks.
Quick recap: what is a lookbehind?
A lookbehind is a zero-width assertion that requires the text before your current position to match (positive lookbehind) or not match (negative lookbehind) a subpattern - but it doesn’t include that text in the matched result.
- Positive lookbehind:
(?<=pattern)- assert the preceding text matchespattern. - Negative lookbehind:
(?<!pattern)- assert the preceding text does not matchpattern.
These let you match based on context without capturing or consuming the context. That makes matches cleaner and less error-prone.
Why lookbehinds matter - three everyday scenarios
- Extract values that follow a label without including the label.
- Example: get the number after
USDor$.
- Example: get the number after
- Match tokens that are not preceded by some prefix.
- Example: match
baronly when it is not preceded byfoo.
- Example: match
- Avoid downstream index arithmetic and capturing-group juggling.
- Cleaner code. Fewer off-by-one mistakes. Easier maintenance.
Short version: lookbehinds make intent explicit. They reduce fragile post-processing.
Syntax and simple examples
Positive lookbehind - grab amount after $:
const text = 'Total: $42.50';
// Using a positive lookbehind, the $ is required but not included in the match
const m = text.match(/(?<=\$)\d+(?:\.\d+)?/);
console.log(m[0]); // -> '42.50'Negative lookbehind - match bar not preceded by foo:
const s = 'bar foo bar foobar';
// match 'bar' that is not immediately preceded by 'foo'
const matches = s.match(/(?<!foo)bar/g);
console.log(matches); // -> ['bar', 'bar'] (the 'foobar' occurrence is ignored)Combining boundaries - word after # but not part of ## header (Markdown-like):
const md = '# heading\n## sub\n### deeper';
const tags = md.match(/(?<=\s|^)#(?!#)\w+/g); // actual example would need tweaks, but shows combinationNotes:
- Regex syntax uses
(?<=...)and(?<!...). - Use regex literals (
/.../) or theRegExpconstructor. When using strings, escape backslashes normally.
Browser and Node compatibility - the real-world gotchas
Lookbehinds were standardized as part of recent ECMAScript editions and are supported by modern JavaScript engines. That said:
- Not all environments historically supported them. Internet Explorer never supported lookbehinds. Older mobile browsers and very old Node versions may not support them either.
- Behavior around variable-length lookbehinds can differ between engines. Some engines enforced fixed-length lookbehind; others relaxed this later. Relying on variable-length lookbehind without testing can break cross-environment.
Check live compatibility before shipping: see the compatibility tables on MDN and Can I Use:
- MDN - Lookbehind assertions: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Lookbehind_assertions
- Can I Use - RegExp lookbehind: https://caniuse.com/regexp-lookbehind
Recommendation: if your user base includes old browsers (or legacy embedded WebViews), or if server-side code may run on older Node versions, test or provide a fallback.
Performance considerations
- Lookbehinds are zero-width assertions; they typically perform well for fixed-length patterns.
- Complex, long, or nested lookbehinds can increase backtracking and hurt performance.
- If you need to run a regex millions of times, benchmark with realistic data.
Rule of thumb: prefer simplicity. Try to keep lookbehind expressions concise and fixed-length where possible.
Practical workarounds when lookbehinds aren’t available
If you must support environments without lookbehind, you can usually rewrite the regex or use a small bit of imperative code.
- Use a capturing group and discard the context in post-processing
// Instead of (?<=\$)\d+, use:
const re = /\$(\d+(?:\.\d+)?)/g;
const text = 'Cost: $12.00';
const m = re.exec(text);
console.log(m[1]); // '12.00'- Use a replace callback to extract the captured portion
const s = 'a:1 b:2 c:3';
// Get numbers after label using capture group
const arr = [];
s.replace(/\w+:(\d+)/g, (_, num) => {
arr.push(num);
});
console.log(arr); // ['1','2','3']- Use split + map where structure is simple
const csv = 'name,age,city\nBob,42,NY';
// If you only need the second column, split lines then split columns
const ages = csv
.split('\n')
.slice(1)
.map(line => line.split(',')[1]);- If you need a negative-context match but cannot use negative lookbehind, match the broader context and filter in JS:
const s = 'bar foo bar foobar';
// match any 'bar' occurrences, then filter those preceded by 'foo'
const result = [];
for (const m of s.matchAll(/bar/g)) {
const idx = m.index;
if (!/foo$/.test(s.slice(Math.max(0, idx - 3), idx))) {
result.push(m[0]);
}
}
console.log(result); // ['bar','bar']These fallbacks are slightly more verbose but widely compatible.
Compact examples for common tasks
- Extract domain extension (e.g.
example.com->com) without including the dot:
const domain = 'example.com';
const ext = domain.match(/(?<=\.)[a-z]+$/i)[0];
// fallback: /\.([a-z]+)$/ and use captured group 1- Remove a character only when not preceded by a digit (negative lookbehind):
// Remove commas that are NOT thousands separators (i.e., not preceded by a digit)
const s = 'abc,123, 1,234';
const cleaned = s.replace(/(?<!\d),/g, '');- Match a word only when preceded by a date marker like
on:
const str = 'We will meet on Monday and on Tuesday';
const day = str.match(/(?<=on\s)\w+/g); // ['Monday', 'Tuesday']When to prefer lookbehind and when to avoid it
Prefer lookbehind when:
- The matching logic depends on preceding context and you want the match result to exclude that context.
- Using lookbehind makes the regex clearer and avoids downstream slicing or index arithmetic.
Avoid lookbehind when:
- You must support ancient browsers or very old Node versions without transpilation or server guarantees.
- Your lookbehind is complex, variable-length, and causes performance issues - consider restructuring.
If you choose to use lookbehind in code that runs in mixed environments, provide a graceful fallback or feature-detection-based alternative.
Feature detection snippet
If you need to guard runtime usage, you can feature-detect lookbehind support safely:
function supportsLookbehind() {
try {
new RegExp('(?<=a)b');
return true;
} catch (_) {
return false;
}
}
console.log('Lookbehind supported?', supportsLookbehind());Use that to choose between the concise lookbehind regex and a compatible fallback.
Final takeaway
Lookbehinds give you expressive, readable ways to assert preceding context without capturing it. They simplify many common parsing tasks and reduce fragile post-processing. But they’re a modern feature: test for support, avoid overly complex variable-length assertions unless you control the runtime, and provide fallback strategies where necessary. Use them to make intent clearer - and keep your regexes maintainable.
References
- MDN - Lookbehind assertions: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Lookbehind_assertions
- Can I Use - RegExp lookbehind: https://caniuse.com/regexp-lookbehind



