Skip to content

\b and \B — a boundary is wherever \w says it is

One line: \b succeeds between a \w character and a non-\w one (or the subject's edge), and \B where \b does not — so every engine's disagreement about what \w covers becomes a disagreement about where words begin, and Ruby manages to disagree with itself.

Level: reference · one construct, eight engines

What it means

A boundary is a position, not a character. \bcat\b matches cat in concat cat at offset 7, not at offset 3, because at offset 3 the character before is n — a word character — so there is no boundary. That much is the same everywhere. What differs is which characters count as \w:

Engine \w by default so \b sits beside…
Python str, Perl (decoded text), Rust regex Unicode letters, digits, marks, _ any Unicode word
JavaScript, Java, Go, PCRE2 (so ABAP) ASCII [A-Za-z0-9_] ASCII words only — é is a boundary
Ruby \w is ASCII, but \b is Unicode-aware the two disagree with each other

Measured

Verified output of kw_word_boundary_sh.sh — regenerated by tools/run_examples.py, never hand-typed.

construct              python          perl            node   ruby   java   go     rust            fancy
whole word             cat             cat             cat    cat    cat    cat    cat             cat
not inside a word      no              no              no     no     no     no     no              no
\B inside a word       cat             cat             cat    cat    cat    cat    cat             cat
Greek word, \b\w+\b    \u{3B1}\u{3B2}  \u{3B1}\u{3B2}  no     no     no     no     \u{3B1}\u{3B2}  \u{3B1}\u{3B2}
Latin-1 word           na\u{EF}ve      na\u{EF}ve      na     no     na     na     na\u{EF}ve      na\u{EF}ve
boundary before digit  2               2               2      2      2      2      2               2
GNU \< \> spelling     <cat>           <cat>           <cat>  <cat>  <cat>  <cat>  cat             cat
\b inside a class      \u{8}           \u{8}           \u{8}  \u{8}  -      -      -               \u{8}

Reading the table

  • Rows 1–3 are the part every engine agrees on: whole-word cat, no cat inside concat, and \B finding exactly the one \b refuses.
  • Row 4, \b\w+\b on αβ. Python, Perl and Rust find the word. JavaScript, Ruby, Java and Go find nothing — their \w has no Greek in it, so there is no run of word characters to bound.
  • Row 5, naïve. The ASCII engines match na and stop at the ï: JavaScript, Java and Go answer na. Ruby answers no — its \w stops at na like theirs, but its \b knows ï is a letter, so the position between a and ï is not a boundary and the match fails. Adding \b to a Ruby pattern can remove matches that were there without it; the lesson counts one word where Python counts two and JavaScript three.
  • Row 7, GNU's \< and \>. The grep and vim spelling of start-of-word and end-of-word is a literal < and > in six engines — they matched <cat> including the brackets — and Rust's regex treats them as word boundaries (so fancy-regex, which delegates, does too), matching cat. Everything else that looks like support is a coincidence of subject.
  • Row 8, [\b]. Inside a class, \b is a backspace character in Python, Perl, JavaScript, Ruby and fancy-regex, and a compile error in Java, Go and Rust's regex. There is no engine in which it is a boundary inside brackets.

The trap

Word boundaries look like they are about spaces and punctuation. They are about \w, and \w is the single most engine-dependent shorthand in the language. A \b-delimited search that works on English words silently skips every word containing an accent in half of these engines, and in Ruby it can skip a word that a plain \w+ would have found.

Where a boundary must work for non-ASCII text: in Java use (?U); in JavaScript there is no flag that helps — build the boundary from lookaround, (?<![\p{L}\p{N}_])…(?![\p{L}\p{N}_]) with u; in PCRE2 and ABAP, (*UCP).

Where it is measured in depth

See also