toolhq.io

All posts
5 min readby Jameel Haider

Regex word boundaries explained

The \b anchor matches a position, not a character. It sits at the boundary between a word character and a non-word character, which is what makes \bcat\b match cat in a cat sat but not inside category. Understanding exactly where that position falls, and where its definition breaks down, saves a lot of confusion.

What counts as a word character

A boundary is defined entirely by the \w class, which in the ASCII sense is [A-Za-z0-9_]. A boundary exists at any point where one side is a \w character and the other side is not, including the start and end of the string if the adjacent character is a word character.

So in the string (cat), boundaries exist between ( and c, and between t and ). The pattern \bcat\b matches because the parentheses are non-word characters sitting next to word characters.

PositionLeft sideRight sideBoundary?
start of catnothingcyes
inside category after catteno
cat. between t and .t.yes
_cat between _ and c_cno

That last row trips people up: underscore is a word character, so there is no boundary between _ and c. \bcat will not match the cat in _cat.

The B anchor

\B is the exact negation: it matches any position that is not a word boundary. \Bcat\B matches cat only when it is surrounded by other word characters, as in scathing. It is less common but useful for finding a substring embedded inside a larger word.

Why boundaries break on accented text

Here is the gotcha that causes real bugs. In most engines the default \w is ASCII only, so accented and non-Latin letters are treated as non-word characters. That means \b sees a boundary in the middle of a word.

Take the name café. The é is not in [A-Za-z0-9_], so there is a boundary between caf and é. The pattern \bcafé\b may fail to match as a whole word in the way you expect, and \w+ will stop at caf. The same happens with naïve, Zürich, Cyrillic, Greek, or Han characters. Your pattern silently splits words at every accented letter.

Practical fixes

The fix depends on your engine.

  • JavaScript: there is no Unicode-aware \b. Work around it by matching explicit letter ranges with the u flag, or use lookarounds against \p{L}, for example (?<!\p{L})café(?!\p{L}) with the u flag. The Unicode property \p{L} matches any letter in any script.
  • PCRE, PHP, Java: pass the Unicode flag so \w and \b become Unicode aware. In PCRE this is (*UCP) or the u modifier; in Java use Pattern.UNICODE_CHARACTER_CLASS.
  • .NET: \w is Unicode aware by default, so \b generally does the right thing with accented text without extra flags.
  • Python re: strings are Unicode by default in Python 3, so \w already includes accented letters unless you pass re.ASCII.

The portable approach, when you cannot rely on Unicode-aware boundaries, is to replace \b with explicit lookarounds that test for the absence of a letter on each side. It is more verbose but it matches your actual intent: a word edge, defined by whatever your language considers a letter.

To see exactly where a boundary falls, paste accented and plain text side by side and watch the highlights shift. The Regex Tester makes these invisible positions visible.