cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Anchors, boundaries & lookaround

Learn JavaScript regex anchors, word boundaries, lookahead, lookbehind, and inline modifiers by matching positions instead of characters.

By the end, you can
  • 01
    Match positions deliberatelyTrace where ^, $, \b, \B, and proposed buffer anchors can stand.
  • 02
    Validate whole inputsChoose anchors for entire fields instead of accidentally finding a substring inside them.
  • 03
    Read lookaround-heavy regexesExplain positive and negative lookahead, lookbehind, captures inside them, and inline modifiers.

Positions, not characters

Regex lessons often begin with characters: letters, digits, classes, and repeats. This lesson switches the question. Instead of asking “which character can I eat next?”, anchors, boundaries, and lookaround ask “is the engine standing in the right place?”

Plain definition

A zero-width assertion is a regex part that checks a position and consumes no characters. Anchors such as ^ and $, word boundaries such as \b, and lookarounds such as (?=...) all match places between characters.

Real-life analogyA security guard at a doorway

Think of a zero-width assertion as a guard posted in a doorway. People move through the doorway, but the guard is not one of the people. A regex engine can stand at a boundary, check what is before or after it, and still consume nothing.

In real life: The guard stands between rooms
In JavaScript: A zero-width assertion stands between characters
In real life: The guard checks a badge
In JavaScript: Lookahead or lookbehind checks nearby text
In real life: The guard does not become part of the group
In JavaScript: The assertion is not included in the consumed match

Where the analogy stops: A guard can remember a person and make decisions. A regex assertion only checks the nearby source text and then the engine continues from the same position.

We will stay in this lane: anchors, boundaries, lookahead, lookbehind, and inline modifiers. For the surrounding regex tools, revisit patterns and flags, character classes, quantifiers, and groups and backreferences.

Zero-width tools in this lesson
ToolQuestion it answersConsumes text?
AnchorsAm I at the start or end of an input or line?No
Word boundariesAm I between a word and non-word character?No
LookaroundDoes text before or after this point match another pattern?No
Inline modifiersShould flags apply only inside this group?The group may contain consuming tokens, but the modifier itself consumes nothing

^, $ and multiline mode

STEP THROUGH

^ and $ are the first anchors most JavaScript developers meet. Without the m flag, ^ is only the start of the whole input and $ is only the real end of the whole input. With m, they can also stand at line boundaries inside the same string.

Step where ^ and $ can stand
Step 0 of 5Ready
Your turn: follow the blue line

Step through the same source with and without the m flag. Watch how the anchors move from the whole buffer to each line.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
const pattern = /^beta$/m;const match = pattern.exec(text); console.log(match ? match.index : null);console.log(match ? match[0] : "no match");
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Choose the anchor mode

Changing the flag starts a fresh replay. Predict whether line 5 logs an index or `null`.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
How ^ and $ change with the m flag
AnchorWithout mWith m
^Matches only index 0 of the whole input.Also matches just after a line terminator.
$Matches only the real end of the whole input in JavaScript.Also matches just before a line terminator.
Final \n subtlety/end$/.test("end\n") is false in JavaScript./end$/m.test("end\n") is true because $ can stand before the newline.
ValidationUse without m for a whole input.Use with m when each line is its own record.

The final-newline detail is worth testing because different regex traditions disagree. In JavaScript, $ without m does not also match just before a final \n. The pattern must include the newline, or you must use multiline mode when line endings are the thing you want.

JavaScript $ and a final newlinePop out in the code editor (opens in a new tab)JavaScript
console.log(/end$/.test("end\n"));console.log(/end\n$/.test("end\n"));console.log(/end$/m.test("end\n"));

Use unflagged anchors when validating a whole field. Use multiline anchors when scanning a log, textarea, or source file line by line. A bare search finds inside a value; an anchored search validates the whole value.

Finding inside vs validating the whole inputPop out in the code editor (opens in a new tab)JavaScript
const value = "say admin now";console.log(/admin/.test(value));console.log(/^admin$/.test(value));console.log(/^admin$/.test("admin"));

Word boundaries and proposed buffer boundaries

INTERACTIVE

\b is a boundary between a JavaScript regex “word character” and a non-word character. In practice, that word side is ASCII-style: letters A through Z, a through z, digits, and underscore. The opposite assertion, \B, matches positions that are not word boundaries.

Visualize boundary positions
Boundary marker sourcePop out in the code editor (opens in a new tab)JavaScript
const text = "hi\ncafé cat\nend\n";console.log([...text.matchAll(/^/gm)].map((m) => m.index));console.log([...text.matchAll(/$/gm)].map((m) => m.index));console.log([...text.matchAll(/\b/g)].map((m) => m.index));
Position map7 matches
0^mh
1·i
2$mLF
3^mc
4·a
5·f
6·é
7·space
8·c
9·a
10·t
11$mLF
12^me
13·n
14·d
15$mLF
16^m $mend
Try it yourself
80 characters max; regexes are fixed by the lesson.

With m, the same anchors also appear around line breaks, so line-oriented scans can work.

This is a conceptual map built from real matchAll positions. Boxes are positions between code units; labels above them are zero-width matches.

That ASCII basis matters with real names and languages. The word café looks like one word to a person, but \b does not treat é as an ASCII word character, even with the u flag. For Unicode words, use lookaround with Unicode property escapes from the character classes lesson.

ASCII \b and a Unicode-friendly alternativePop out in the code editor (opens in a new tab)JavaScript
console.log(/\bcafé\b/u.test("café"));console.log(/(?<!\p{L})café(?!\p{L})/u.test("café"));console.log(/\Bend/.test("weekend"));
Buffer boundaries are a proposal

As of September 26, 2026, TC39’s RegExp Buffer Boundaries proposal for \A, \z, and \Z is Stage 3. It is not supported in Node 22 or Chrome 154 today. \A would mean start of the whole buffer, \z absolute end, and \Z end or just before one final line terminator.

Proposal syntax: \A, \z, and \ZJavaScript
// Proposal syntax: not supported in Node 22 or Chrome 154 today.const log = "status: ok\n";console.log(/\Astatus: ok\Z/u.test(log)); // would allow one final line terminatorconsole.log(/\Astatus: ok\z/u.test(log)); // would require the absolute end

Today, use ^ and $ without m for most whole-input checks, or the absolute-position lookarounds below when you need an anchor that cannot be affected by flags around it.

Working whole-buffer equivalents todayPop out in the code editor (opens in a new tab)JavaScript
const exact = "status: ok";const withFinalNewline = "status: ok\n"; console.log(/^status: ok$/.test(exact));console.log(/(?<![\s\S])status: ok(?![\s\S])/.test(exact));console.log(/(?<![\s\S])status: ok(?![\s\S])/.test(withFinalNewline));

Lookahead: check what comes next

STEP THROUGH

Positive lookahead (?=...) says “the next text must match this.” Negative lookahead (?!...) says “the next text must not match this.” Either way, the engine stays at the same position after the assertion succeeds.

Lookahead does not consumePop out in the code editor (opens in a new tab)JavaScript
const text = "ababa";const starts = [...text.matchAll(/(?=aba)/g)].map((match) => match.index);console.log(starts.join(","));console.log(text.replace(/(?=aba)/g, "|")); const captured = /(?=(aba))/.exec(text);console.log(captured[0].length);console.log(captured[1]);

The first line finds two start positions for aba in ababa: 0 and 2. The replacement inserts markers at those positions without deleting any letters. The final two logs show a subtle but useful rule: captures inside a positive lookahead still capture. The whole assertion match is empty, but group 1 holds aba.

Step through lookahead password rules
Step 0 of 4Ready
Your turn: follow the blue line

Lookahead lets several rules inspect the same input before the consuming part runs. Step through the three passwords.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
console.log(strong.test("desk-2"));console.log(strong.test("Desk 2026"));console.log(strong.test("Desk-2026"));
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Lookahead is also useful for formatting. The thousands separator pattern places a comma at a non-boundary position only when complete groups of three digits are ahead and no extra digit follows that group.

Thousands separators with \B and lookaheadPop out in the code editor (opens in a new tab)JavaScript
const value = "1234567890";console.log(value.replace(/\B(?=(\d{3})+(?!\d))/g, ","));

Lookbehind: check what came before

PRACTICAL

Positive lookbehind (?<=...) says “the previous text must match this.” Negative lookbehind (?<!...) says “the previous text must not match this.” Current Chrome, Firefox, Edge, and Safari support lookbehind; Safari support begins at 16.4, so old Safari is the main browser caveat.

Extract numbers after a dollar signPop out in the code editor (opens in a new tab)JavaScript
const receipt = "Tea $3.50, mug $12, tax 0.90";const prices = [...receipt.matchAll(/(?<=\$)\d+(?:\.\d{2})?/g)].map((match) => match[0]);console.log(prices.join(", "));

The dollar sign is checked but not consumed. That makes lookbehind a clean fit for extraction: keep context out of the returned match without using a separate capturing group after the match.

Try practical lookaround patterns
Selected practical patternPop out in the code editor (opens in a new tab)JavaScript
const strong = /^(?=.{8,}$)(?=.*[A-Z])(?=.*\d)(?!.*\s).+$/;console.log(strong.test("desk-2"));console.log(strong.test("Desk 2026"));console.log(strong.test("Desk-2026"));
Outputpassword

passes

Try it yourself

The anchors make this a whole-password rule. Lookaheads check length, uppercase, digit, and no spaces before .+ consumes.

Inputs are bounded and the regexes are fixed. This keeps the playground focused on reading assertions, not evaluating arbitrary patterns.

Inline modifiers: flags for only one group

ES2025

Inline modifiers use syntax such as (?i:...), (?-i:...), and (?m:...) to turn flags on or off for one group. They are ES2025 syntax. The current compatibility picture is uneven: current Chrome and Firefox support them, Safari does not yet according to MDN compatibility data, and Node 22 throws “Invalid group.”

Inline modifier feature check
Inline modifier sourcePop out in the code editor (opens in a new tab)JavaScript
// ES2025 inline modifiers. Chrome 154 and current Firefox parse this; Node 22 and Safari do not.const route = /^(?i:api)-(?-i:v1)$/i;console.log(route.test("API-v1"));console.log(route.test("API-V1"));
Feature resultchecking
API-v1checking support
API-V1checking support
Try it yourself
Feature detected with `new RegExp`

Checking this browser before building the regex keeps server rendering and hydration stable.

The displayed source uses the new syntax. The panel itself feature-detects support before building a regex, so unsupported browsers still render the lesson.

The fallback below shows why this feature is useful. A plain global i flag can make an entire pattern case-insensitive, but it cannot make only api loose while keeping v1 exact.

Fallback with a whole-pattern flagPop out in the code editor (opens in a new tab)JavaScript
const route = /^api-v1$/i;console.log(route.test("API-v1"));console.log(route.test("API-V1")); // cannot keep only v1 case-sensitive

Practical patterns you will actually write

SORT IT

These assertions are not trivia. They separate “find this somewhere” from “this whole value is valid,” keep prefixes out of extracted text, and make replacement patterns land at positions rather than over characters.

Real-world assertion choices
Use casePattern shapeWhy
Whole-field validation/^admin$/Rejects say admin now; good for one field value.
Line scanning/^ERROR:/gmFinds records that begin a line in a log.
Password rules^(?=.{8,}$)(?=.*\d).+$Several assertions inspect the same input before consuming it.
Whole-word highlighting\bcat\b or Unicode lookaroundUse \b for ASCII words; use \p{L} lookaround for real words.
Extract prices(?<=\$)\d+(?:\.\d{2})?Keeps the dollar sign out of the match.
Consumes characters or checks a position?
  • ^
  • \b
  • \d+
  • (?<=\$)
  • \A
  • admin
Try it yourself
0 of 6 correct

Sort each regex piece by what it does. Proposal-only items are not supported by today's engines in this lesson.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common misconceptions

  • “$ always matches before a final newline.” Not in JavaScript without m. Test it before copying advice from another regex flavor.
  • “^...$ is always validation.” It validates the whole input only when m is off and your pattern handles every allowed character.
  • “\b means human word.” It is based on ASCII-style word characters, so names like café need a Unicode-aware alternative.
  • “Lookaround eats the text it checks.” It does not. Consuming tokens after the assertion decide what appears in the match.
  • “Unsupported regex syntax is harmless if it is never reached.” Syntax in a regex literal is parsed before code runs. Store new syntax as a string and feature-detect when a runtime may not parse it.
The four lookarounds
SyntaxNameConditionCommon use
(?=...)Positive lookaheadThe next characters must match, but they are not consumed.Password rules, separators before a group
(?!...)Negative lookaheadThe next characters must not match.Reject whitespace or stop a digit run
(?<=...)Positive lookbehindThe previous characters must match, but stay outside the result.Numbers after $
(?<!...)Negative lookbehindThe previous characters must not match.Unicode whole-word checks with \p{L}

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upPredict $ with a final newline

Read the three tests and type the three booleans in order.

Starter codePop out in the code editor (opens in a new tab)JavaScript
console.log(/end$/.test("end\n"));
console.log(/end\n$/.test("end\n"));
console.log(/end$/m.test("end\n"));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeChoose the Unicode-friendly word check

    Which of the two logged checks is the Unicode-friendly whole-word test?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    console.log(/\bcafé\b/u.test("café"));
    console.log(/(?<!\p{L})café(?!\p{L})/u.test("café"));

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFormat a number

      Predict the formatted string.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const value = "1234567";
      console.log(value.replace(/\B(?=(\d{3})+(?!\d))/g, ","));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeExtract prices without the dollar sign

        Type the two extracted prices in order.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const receipt = "Tea $3.50, mug $12, tax 0.90";
        const prices = [...receipt.matchAll(/(?<=\$)\d+(?:\.\d{2})?/g)].map((match) => match[0]);
        console.log(prices.join(", "));

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeName the password-rule technique

          Which assertion technique checks several rules before the final consuming part of the regex?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const strong = /^(?=.{8,}$)(?=.*[A-Z])(?=.*\d)(?!.*\s).+$/;
          console.log(strong.test("Desk-2026"));

          Answer, then press Check. Spacing and letter case don’t matter.

            Check your understanding

            7 QUESTIONS
            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7What makes anchors and lookaround different from a token like \d?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does JavaScript print for the final-newline check?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log(/end$/.test("end\n"));

              Choose an answer to see the explanation.

            3. Question 3 of 7What does multiline mode change here?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log(/^beta$/m.test("alpha\nbeta\ngamma"));

              Choose an answer to see the explanation.

            4. Question 4 of 7Why does this word-boundary check fail?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log(/\bcafé\b/u.test("café"));

              Choose an answer to see the explanation.

            5. Question 5 of 7What does the thousands-separator pattern print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log("1234567".replace(/\B(?=(\d{3})+(?!\d))/g, ","));

              Choose an answer to see the explanation.

            6. Question 6 of 7What does the price lookbehind extract?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const text = "Tea $3.50, mug $12";
              console.log([...text.matchAll(/(?<=\$)\d+(?:\.\d{2})?/g)].map((m) => m[0]).join(", "));

              Choose an answer to see the explanation.

            7. Question 7 of 7Which statement is honest about inline modifiers today?

              Choose an answer to see the explanation.

            Key takeaways

            • Anchors, boundaries, and lookaround are zero-width assertions: they check positions.
            • JavaScript $ without m matches the real end, not the spot before a final newline.
            • \b is ASCII-oriented; use Unicode property lookaround for international words.
            • Lookahead and lookbehind do not consume, but captures inside successful positive lookaround can still capture.
            • Inline modifiers are useful ES2025 syntax, but feature-detect because Node 22 and Safari do not parse them yet.

            Remember the one-liner.
            Assertions let a regex match the place where something is true, not only the characters that should be returned.

            Up next: Regex performance & safety, where backtracking, ReDoS, and the sticky y flag explain why some patterns are safer than others.

            CompleteFrontend Clear concepts. Working examples.