Free · Fast · Privacy-first

Regex Lookahead and Lookbehind Tester, Zero-Width Assertions

Lookahead and lookbehind assertions are the most powerful and most misunderstood regex feature.

Test positive and negative lookaheads: (?=...) and (?!...)

🔒

Test positive and negative lookbehinds: (?<=...) and (?<!...)

See exactly which match positions satisfy the assertion

Uses the V8 engine: ES2018 variable-length lookbehinds are supported

Cost
Free tier
Sign-up
Not required
Processing
Tool-specific
Privacy
Clearly disclosed
IframeResponsiveAttribution included

Add this Regex Tester to your website

Drop the Regex Tester into a blog post, product docs, intranet, or school portal with one iframe. Processing, privacy, and usage limits are the same as on the full tool page.

  • One copy-ready line of HTML
  • Responsive — adapts to any container width
  • No API credentials are placed in the snippet

Embed code

<iframe
  src="https://www.fixtools.io/developer/regex-tester?embed=1"
  width="100%"
  height="780"
  frameborder="0"
  style="border:0;border-radius:16px;max-width:900px;"
  title="Regex Tester by FixTools"
  loading="lazy"
  allow="clipboard-write"
></iframe>

Attribution-friendly: a small "Powered by FixTools" link appears in the embed footer.

Understanding Lookaround Assertions: What They Match and What They Do Not

Lookaround assertions are zero-width: they consume no characters and their content does not appear in the match result. A positive lookahead (?=pattern) asserts that the given pattern can be matched starting at the current position without advancing the cursor. A negative lookahead (?!pattern) asserts the opposite: the pattern must not match at the current position. Lookbehinds work in the same way but look backward from the current position: (?<=pattern) asserts that the given pattern ends at the current cursor position, and (?<!pattern) asserts that it does not. The key practical consequence is that lookarounds allow you to match a character or string only when it appears in a specific surrounding context, without including that context as part of the match itself. This makes the match result cleaner and eliminates the post-processing step of stripping captured context from the extracted value.

A common application of lookbehind is extracting a value that follows a specific label in structured text. Without a lookbehind, you would capture both the label and the value in a group and then discard the label in code. With a positive lookbehind, you assert the label is present and capture only the value: (?<=Price:\s)\d+\.\d{2} matches "49.99" in "Price: 49.99" without including "Price: " in the match. Positive lookaheads are widely used for password complexity enforcement: ^(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z\d]).{8,}$ stacks three lookaheads at position zero to simultaneously require an uppercase letter, a digit, and a special character, without consuming any characters from the string, so the .{8,} anchor at the end can still assert minimum length across the whole password.

JavaScript's V8 engine fully supports ES2018 lookbehinds, including variable-length lookbehinds where the lookbehind pattern can match strings of different lengths. For example, (?<=[A-Z][a-z]\s)\w+ looks backward for an initial followed by a space and captures the word after it. This is not supported in all regex flavours: some older engines require lookbehinds to be fixed-length patterns. Since FixTools runs in V8 in the browser, variable-length lookbehinds work correctly in the tester and you can verify ES2018 lookbehind behaviour directly against your target string without needing to write Node.js code for each iteration.

Lookbehind support varies dramatically across engines, and this is the single largest portability concern for patterns that use them. JavaScript V8 and PCRE2 support variable-length lookbehinds with no fixed-length restriction. PCRE1 and Python's re module require fixed-length lookbehinds, so (?<=ab) works but (?<=ab|abc) fails because the alternatives have different lengths. The Python third-party regex library supports variable-length lookbehinds without restriction. Java added variable-length support in JDK 13. .NET supports variable-length lookbehinds. The practical implication is that a pattern using variable-length lookbehind tested in the browser may compile but throw at runtime in older Python or older Java. When you need cross-engine portability, restrict your lookbehinds to fixed-length forms, or use a capturing group with a discard in your application code as the universally-supported alternative.

How to use this tool

💡

Enter a pattern with lookahead or lookbehind assertions and paste your test string. The tester highlights only the portions that satisfy all assertions, showing zero-width assertion behaviour in real time.

How It Works

Step-by-step guide to regex lookahead and lookbehind tester, zero-width assertions:

  1. 1

    Open the Regex Tester

    Navigate to the FixTools Regex Tester using the link on this page. The tool runs in the browser V8 engine, which fully supports ES2018 lookbehind assertions including variable-length forms. No setup is needed beyond opening the page.

  2. 2

    Enter a lookaround pattern

    Type a pattern with a lookahead or lookbehind assertion. For a lookbehind example, enter (?<=Price:\s)\d+\.\d{2} to match a price after its label. For a multi-condition lookahead example, enter ^(?=.*[A-Z])(?=.*\d).{8,}$ to test a password complexity rule requiring an uppercase letter and a digit.

  3. 3

    Paste your test string

    Enter text that includes both cases where the assertion should pass and cases where it should fail. For the price example, include "Price: 49.99" and "Discount: 10.00" to confirm that only the price value after the correct label is matched. For the password example, include both compliant and non-compliant strings on separate lines with the m flag enabled.

  4. 4

    Verify assertion behaviour

    Check that only the correct positions are highlighted in the test string. The lookaround context, the text inside (?=...), (?!...), (?<=...), or (?<!...), should not appear in the highlighted match. If the context text is highlighted, the assertion content has likely been placed inside the main pattern rather than inside the lookaround delimiters.

Real-world examples

Common situations where this approach makes a real difference:

Extracting prices after currency labels without including the label in the match

A web scraper needs to extract numeric price values from product pages where prices appear as "Price: 49.99" or "Cost: 129.00" in the page text. The pattern (?<=(Price|Cost):\s)\d+\.\d{2} uses a variable-length lookbehind to assert that either label precedes the number, capturing only the numeric value. Testing in the tester confirms that 49.99 is matched from "Price: 49.99" and 129.00 from "Cost: 129.00", and that no post-processing is needed to strip the label since it never appears in the match output.

Enforcing password complexity with multiple positive lookaheads

A security policy requires passwords to contain at least one uppercase letter, one digit, and one special character with a minimum length of eight characters. The pattern ^(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$ uses three lookaheads stacked at position zero, each checking a different condition independently without consuming characters. Testing confirms "Password1!" passes all three conditions, "password1!" fails the uppercase check, "Password!" fails the digit check, and "Pass1!" fails the minimum length check, all in a single tester run.

Matching words that are not preceded by a specific prefix

A content filter needs to match occurrences of the word "script" in HTML text but must not match when it appears as part of a type attribute value like type="text/javascript". The pattern (?<!type=\")script matches standalone occurrences and tag names while ignoring the attribute value context. Testing against a sample HTML snippet containing both a bare <script> tag and a type="text/javascript" attribute confirms that the tag is flagged but the attribute value is correctly skipped by the negative lookbehind.

Splitting on commas only when not inside parentheses using a lookahead

A function signature parser needs to split argument lists by commas but must not split on commas inside nested parentheses representing inner function calls or tuple arguments. The pattern ,(?![^(]*\)) uses a negative lookahead to assert there is no unmatched closing parenthesis ahead between the comma and the next opening parenthesis, detecting only top-level commas. Testing against "func(a, b, nested(c, d), e)" confirms the split points fall after b and after the nested(...) group but not inside it.

When to use this guide

Use this page when you are writing patterns that use (?=...), (?!...), (?<=...), or (?<!...) assertions and want to verify they are triggering at the correct positions in your test string.

Pro tips

Get better results with these expert suggestions:

1

Use multiple lookaheads at position zero for AND conditions

To assert that a string simultaneously satisfies several independent conditions, stack positive lookaheads at position zero: ^(?=.*X)(?=.*Y)(?=.*Z).{min,}$. Each lookahead fires at position 0, checks its own condition independently, and returns the cursor to position 0 before the next lookahead runs. This cleanly expresses AND logic across multiple conditions without alternation or nested groups, and is easy to extend with additional conditions by adding another lookahead to the chain.

2

Test that lookaround context does NOT appear in the match

The fundamental property of lookaround assertions is that the asserted context is not consumed and does not appear in the match. Verify this directly in the tester: the highlighted portion should contain only the tokens that are part of the main pattern, not the text inside the lookaround parentheses. If the context text is appearing in the highlighted match, you may have placed the tokens inside the main pattern accidentally rather than inside the lookaround delimiters.

3

Prefer lookbehind over a capturing group and discard for label-value extraction

Instead of writing (label)\s*(value) and then ignoring the first group in your code, write (?<=label\s*)value. The lookbehind approach produces a simpler match object containing only the value, requires no post-processing to strip the label, and makes the pattern's intent clear at a glance. Test both the capturing group approach and the lookbehind approach in the tester side by side to confirm they produce the same matched value before choosing the cleaner form for production code.

4

Use the s flag with lookaheads that need to match across line boundaries

Lookahead and lookbehind patterns are subject to the same flag constraints as the rest of the regex. If the assertion content needs to span a newline character, enable the s (dotAll) flag so that the dot inside the lookaround matches newlines as well as other characters. Without s, a lookahead like (?=.*end) will not match "end" on a subsequent line because the dot stops at the line boundary, causing the assertion to fail even when "end" appears later in the string.

FAQ

Frequently asked questions

A lookahead (?=...) asserts that the pattern inside the parentheses can be matched starting at the current position, looking forward in the string. A lookbehind (?<=...) asserts that the pattern can be matched ending at the current position, looking backward. Both are zero-width: they test context without consuming characters or including the context in the match result. Negative variants (?!...) and (?<!...) assert that the respective pattern does not match at the checked position.
This is the intended and correct behaviour of lookaround assertions. The content inside (?=...), (?!...), (?<=...), or (?<!...) is used exclusively to determine whether the assertion passes or fails at the current position. It is never consumed by the engine and never appears in the match string or capture groups. If you need the context content in your result, move it outside the lookaround into the main pattern where it will be consumed and can be placed in a capture group.
Yes, from ES2018 onward. The V8 engine in Chrome 62, Edge 79, and Node.js 8 and later supports lookbehinds where the pattern inside the lookbehind can match strings of different lengths, such as (?<=[A-Z]+\s). This was not supported in older engines like Internet Explorer or older versions of Ruby and .NET, where lookbehinds are restricted to fixed-length patterns. Since FixTools runs in the browser V8 engine, variable-length lookbehinds work correctly in the tester.
Yes. You can use a lookbehind before the main pattern and a lookahead after it to assert context on both sides simultaneously. For example, (?<=\$)[\d,]+(?=\s*USD) matches a number that is preceded by a dollar sign and followed by " USD", capturing only the number itself. Test combined lookaround patterns in the tester with inputs that satisfy both assertions, inputs that satisfy only one, and inputs that satisfy neither, to confirm all four cases behave correctly.
A negative lookahead (?!pattern) asserts that the specified pattern does not match at the current position. It is useful for excluding specific contexts from an otherwise broad match: \bcat(?!fish) matches "cat" and "catalog" but not "catfish". For password validation, (?!.*password) placed at the start of a pattern rejects any string containing the word "password". For parsing, (?!\s) before a token assertion ensures the token cannot begin with a whitespace character.
ES2018 lookbehind assertions are not supported in Internet Explorer or in Safari versions before 16.4. If you must support these environments, lookbehinds are not an option. Instead, use a capturing group to match the context along with the value, and then extract the relevant group from the match result in your code. Test the capturing group alternative in the tester to confirm it captures the same value before using it as the compatibility fallback.
A negative lookbehind can check that a match is not immediately preceded by a quote character: (?<!["'])\bword\b matches "word" only when it is not preceded by a single or double quote. This is useful in code analysis patterns that should not trigger inside string literals. Test the pattern against samples with the word appearing both inside and outside quote contexts to confirm the assertion correctly suppresses matches in the quoted positions.
Yes, but nesting lookarounds quickly becomes difficult to read and maintain. A more practical approach for complex multi-condition assertions is to stack multiple independent lookaheads at position zero rather than nesting them. Test any nested lookaround pattern carefully in the tester against both matching and non-matching inputs, because the interaction between nested zero-width assertions can produce results that are hard to predict by reading the pattern alone. Use the tester to confirm the behaviour empirically before shipping.
Lookaheads themselves do not consume characters, so they cannot directly cause a backtracking explosion the way nested quantifiers do. But a lookahead containing a dangerous sub-pattern can. For example, ^(?=(a+)+b).* places a pattern with nested quantifiers inside a lookahead; the lookahead's internal pattern still runs through the full backtracking engine, and on an input like "aaaaaaaaaaaaaaaaX" the engine spends exponential time inside the lookahead before declaring no match. Audit the contents of lookarounds with the same care you apply to the main pattern. Replace nested quantifiers inside lookaheads with unrolled equivalents, and prefer simple anchored content like (?=\w{8,}) over compound conditions where possible.
Lookarounds are an elegant single-pass solution for simple context assertions, but when you find yourself stacking four or five of them, or nesting a lookbehind inside a lookahead, the pattern often becomes harder to read than two simpler passes. Consider a password rule that requires uppercase, lowercase, digit, special character, no whitespace, no leading digit, and minimum length 12: that is six conditions stacked in lookaheads, and reading the resulting pattern takes longer than just running six discrete checks in code. The practical heuristic is that if the regex with lookarounds takes longer to read than the same logic expressed as a series of test() or character-count checks in code, switch to the multi-pass approach for the sake of the next developer to touch the file.

Related guides

More use-case guides for the same tool:

Ready to get started?

Open Regex Tester to review its free limits and processing method.

Open Regex Tester →

Free tier · No account needed · Transparent limits