Word boundary: \b
Some parts of a regexp don’t match characters at all. They match a position. The word boundary pattern:\b belongs to this family, right alongside pattern:^ (start of string) and pattern:$ (end of string). These are called anchors or zero-width assertions: they consume nothing, they only ask a yes/no question about where you currently stand in the string.
When the regexp engine — the piece of the JavaScript runtime that actually walks through your string looking for a match — reaches a pattern:\b, it doesn’t read a character. It looks at the gap between two characters and asks: “Is this spot the edge of a word?”
What counts as a boundary
A word boundary sits at exactly three kinds of position:
- String start, when the first character is a word character
pattern:\w. - Between two characters, when one side is a word character
pattern:\wand the other side is not. - String end, when the last character is a word character
pattern:\w.
Remember what pattern:\w covers: a Latin letter a-z or A-Z, a digit 0-9, or an underscore _. Everything else — spaces, commas, exclamation marks, hyphens — is a “non-word” character. A boundary is any place where you flip from one kind to the other.
\b. Word chars are dark; the comma and space (shown as ·) are muted non-word chars.Take the regexp pattern:\bRust\b. It finds subject:Rust inside subject:Howdy, Rust! because subject:Rust stands alone there. But it finds nothing in subject:Howdy, Rusty!, because after subject:Rust the next character is subject:y, a word character — no boundary, no match.
alert( "Howdy, Rust!".match(/\bRust\b/) ); // Rust
alert( "Howdy, Rusty!".match(/\bRust\b/) ); // null
Tracing a boundary by hand
Let’s map every boundary in subject:Howdy, Rust!. Walk the string and mark each spot where a word character meets a non-word character or a string edge.
Notice where boundaries do not appear. There’s no boundary between subject:H and subject:o, because both are word characters. There’s none between the comma and the space either, since neither is a word character. And crucially, after the final subject:! there is no boundary, because subject:! isn’t a word character.
Now the pattern pattern:\bHowdy\b matches, and here’s the play-by-play:
- At the very start,
pattern:\bpasses — the first charactersubject:His a word character (that’s boundary 1). - The literal
pattern:Howdymatches the next five characters. pattern:\bpasses again — we’re sitting betweensubject:yand the comma (boundary 2).
That’s a clean match. Contrast it with two patterns that fail:
pattern:\bHowd\bfails. After matchingsubject:Howd, the engine testspattern:\bbetweensubject:dandsubject:y. Both are word characters, so there’s no boundary there.pattern:\bRust!\bfails. It matchessubject:Rust!fine, but then demands a boundary aftersubject:!. Sincesubject:!is a non-word character, no boundary exists after it.
alert( "Howdy, Rust!".match(/\bHowdy\b/) ); // Howdy
alert( "Howdy, Rust!".match(/\bRust\b/) ); // Rust
alert( "Howdy, Rust!".match(/\bHowd\b/) ); // null (no match)
alert( "Howdy, Rust!".match(/\bRust!\b/) ); // null (no match)
The fastest way to build intuition for pattern:\b is to watch it highlight. Edit the pattern and the test string below and see exactly which slices match. Try removing one pattern:\b, or wrapping a word that touches other letters, and notice how the highlights move.
Boundaries work with digits too
Since pattern:\w includes digits, pattern:\b is just as happy sitting next to numbers. This makes it a neat tool for grabbing standalone numbers of an exact length.
The pattern pattern:\b\d\d\b finds two-digit numbers that stand on their own — bounded by spaces, punctuation, or the string edges, but never glued to more digits.
alert( "7 40 512 63".match(/\b\d\d\b/g) ); // 40,63
alert( "81,92,03".match(/\b\d\d\b/g) ); // 81,92,03
Why does the first line skip subject:7, subject:512, and match only subject:40 and subject:63? Walk it through:
\d\d needs two51, no \b before 2 — rejectThe subject:512 case is the interesting one. The engine can match pattern:\d\d against subject:51, but the closing pattern:\b then checks the gap between subject:1 and subject:2. Two digits touching means two word characters, so no boundary — the whole attempt is thrown out.
In the second string, subject:81,92,03, every pair is fenced off by commas, so all three match.
The catch: \b is Latin-only
There’s a real limitation you need to keep in mind. The whole definition of pattern:\b rests on pattern:\w, and pattern:\w only recognizes Latin letters a-z, digits, and underscore. Any character outside that set counts as “non-word,” including letters from most of the world’s writing systems.
Because those letters read as non-word to the engine, a boundary appears at the wrong places — for instance right in the middle of a Cyrillic word, wherever it borders a Latin character, and nowhere along a run of purely non-Latin letters. If you need real Unicode-aware word matching, reach for the pattern:u flag together with Unicode property escapes such as pattern:\p{L} (any letter), and build your own boundary logic from lookarounds rather than trusting pattern:\b.
\b wrongly fires between f and é, because é is not \w. No boundary sits after é at all.Quick reference
| Anchor | Matches the position… |
|---|---|
| ^ | at the start of the string (or line, with the m flag) |
| $ | at the end of the string (or line, with the m flag) |
| \b | between a \w and a non-\w (a word edge) |
| \B | everywhere \b does not — the negation |
The uppercase pattern:\B is the exact inverse: it asserts you are not at a word boundary. It’s handy for finding a pattern only when it’s embedded inside a larger word — for example pattern:\Band\B matches the subject:and buried inside subject:sandy but not the standalone word subject:and.
Switch between the three patterns below over the same sentence and watch which occurrences of subject:and light up:
None of these anchors consume characters. They slot between the characters your other subpatterns match, quietly checking that you’re standing in the right place before the engine moves on.