Everything so far — literals, character classes — matches actual characters. Anchors are different: they match a position in the string without consuming any characters themselves. They let you say “only at the start,” “only at the end,” or “only where a word begins or ends.”
^ and $ — start and end of string
^ anchors a match to the beginning of the string; $ anchors it to the end. Used together, they force the entire string to match the pattern, not just some substring of it.
/^Hello/.test("Hello world"); // true — starts with "Hello"
/^Hello/.test("Say Hello"); // false — "Hello" isn't at the start
/World$/.test("Hello World"); // true — ends with "World"
Multiline mode: the m flag
By default, ^ and $ only care about the very start and very end of the whole string — even if that string spans multiple lines. Add the m flag and they instead anchor to the start and end of every line:
const text = "Hello world\nSay Hello\nHello there";
/^Hello/.test(text); // true — matches at the very start of the string
/^Hello/m.test(text); // still true, but now for a different reason
With the m flag and the g flag (used together in the demo above to find every match, not just the first), ^Hello matches at the start of line 1 and the start of line 3 — but not line 2, because "Say Hello" doesn’t begin with Hello. Without m, only the very first line’s Hello would ever be reachable by ^, no matter how many lines follow.
| Mode | ^ matches |
$ matches |
|---|---|---|
Default (no m) |
Start of the whole string | End of the whole string |
With m flag |
Start of every line | End of every line |
Tip
Don’t confuse multiline mode (m) with dot-matches-newline mode (often called s or “dotall” in other engines) — they control different things. m changes what ^/$ mean; s changes whether . can match a newline character.
\b — word boundaries
\b matches the invisible position between a “word” character (\w: letters, digits, underscore) and a “non-word” character (anything else, including the start/end of the string). It’s what makes “whole word” matching possible without anchoring to the entire string.
/cat/.test("concatenate"); // true — "cat" is just a substring
/\bcat\b/.test("concatenate"); // false — "cat" isn't a whole word here
/\bcat\b/.test("a cat sat"); // true — "cat" stands alone
The demo matches the two standalone occurrences of cat, but correctly skips cats (because there’s no boundary between t and s — both are word characters) and skips the cat hiding inside concatenate.
\B — NOT a word boundary
\B is the negation of \b: it matches a position that is not a word boundary — meaning it’s either fully inside a word (word character on both sides) or fully inside a run of non-word characters.
Here \Bcat\B matches inside concatenate and inside scatter — both have a word character immediately before and after the cat substring — but skips the standalone cat, since spaces surround it on both sides (making those boundaries \b, not \B).
Try it yourself
Using the last demo, change the pattern from \Bcat\B to just cat (no boundaries at all) and compare how many matches you get across the same text.
What should happen
Plain cat matches all three occurrences — inside concatenate, the standalone word, and inside scatter — because it doesn’t care about what surrounds the match. Adding \B on both sides filters that down to only the two “embedded” occurrences, which is exactly the kind of precision anchors are for.
What’s next
Anchors control where a match can start or end, but so far every character in our patterns has appeared exactly once. The next lesson covers quantifiers — *, +, ?, and {n,m} — which control how many times a piece of a pattern can repeat.