Unicode: flag "u" and class \p{...}

JavaScript strings are stored as Unicode text. Under the hood, the engine works with UTF-16: each unit of storage is 2 bytes, and 2 bytes give you 65,536 possible values. That covers a huge amount of the world’s writing, but not all of it.

To reach beyond that limit, some characters are stored using two 2-byte units back to back — 4 bytes total. Mathematical symbols like 𝔸, emoji like 🚀, and many historical scripts live in that extended range.

Here are the code points of a few characters, and how many bytes each one takes in UTF-16:

Character Unicode Bytes in UTF-16
a 0x0061 2
0x2211 2
𝔸 0x1d538 4
𝔹 0x1d539 4
🚀 0x1f680 4

So a and fit in a single 2-byte unit, while 𝔸, 𝔹, and 🚀 each need two units glued together.

a
U+0061
0x0061
1 code unit → length 1
🚀
U+1F680
0xD83D
0xDE80
2 code units → length 2 (a surrogate pair)
A 4-byte character is stored as two 2-byte code units — a surrogate pair — that only mean something together.

When JavaScript was designed, Unicode was smaller and 4-byte characters did not exist. Some parts of the language still carry that old assumption, and they miscount these characters.

The length property is the classic example. It counts code units, not characters:

alert('🚀'.length); // 2
alert('𝔸'.length); // 2

Your eyes see one character. length sees two, because it counts each 2-byte unit separately. For a surrogate pair that’s wrong — the two units are meaningless apart and should be treated as a single character. (The Strings article covers surrogate pairs in more depth.)

Regular expressions inherit the same default behavior: without help, they also chop a 4-byte character into two 2-byte halves. That leads to the same kind of surprises you get with length, and the Sets and ranges […] article shows where it bites in practice.

Type a string below and watch length (which counts 2-byte units) disagree with the number of characters you actually see. Try adding an emoji or a mathematical letter like 𝔸:

interactiveCode units vs. characters

The fix is the pattern:u flag. Add it, and the regexp engine treats a surrogate pair as one character. It also switches on a second feature: Unicode property escapes, pattern:\p{…}, which is the main subject of this article.

Unicode properties \p{…}

Every character in Unicode carries a bundle of metadata — properties. They record which “category” the character falls into and other facts about it.

Two properties come up constantly:

  • Letter — the character is a letter in some alphabet, any language.
  • Number — the character is a digit, whether Arabic, Chinese, or anything else.

You match a character by its property with pattern:\p{…}, and this only works when the pattern:u flag is present.

For example, pattern:\p{Letter} matches a letter in any script. L is an alias for Letter, so pattern:\p{L} does the same thing. Most properties have a short alias like this.

The example below pulls out three letters from three different writing systems — Latin, Greek, and Armenian:

let str = "Z Ω հ";

alert( str.match(/\p{L}/gu) ); // Z,Ω,հ
alert( str.match(/\p{L}/g) );  // null — \p needs the "u" flag to mean anything

Without pattern:u, the pattern:\p{L} syntax isn’t recognized as a property class, so the second match finds nothing.

Try it live below. The demo runs pattern:\p{L} against whatever you type and lists every letter it pulls out — regardless of alphabet. Digits, spaces, and punctuation are ignored:

interactiveExtract letters from any script with \p{L}

Here are the top-level categories and their subcategories. Each one has a one- or two-letter code you can drop straight into pattern:\p{…}:

Letter · L
Ll lower · Lu upper · Lt title · Lm modifier · Lo other
Number · N
Nd decimal · Nl letter · No other
Punctuation · P
Pc connector · Pd dash · Pi initial-quote · Pf final-quote · Ps open · Pe close · Po other
Mark · M
Mc spacing-combining · Me enclosing · Mn non-spacing (accents etc.)
Symbol · S
Sc currency · Sk modifier · Sm math · So other
Separator · Z
Zl line · Zp paragraph · Zs space
Other · C
Cc control · Cf format · Cn unassigned · Co private-use · Cs surrogate
Unicode general categories: a top-level letter (L, N, P, …) plus an optional second letter for the subcategory.

So if you want lowercase letters only, write pattern:\p{Ll}. For any punctuation mark, pattern:\p{P}. Passing just the top-level code (P) matches every subcategory under it; adding the second letter (Pd) narrows it to one.

Beyond the general categories there are derived properties — computed from other data — such as:

  • Alphabetic (Alpha): the union of Letters L, letter-numbers Nl (like Ⅶ, the Roman numeral 7), and a set of Other_Alphabetic (OAlpha) characters.
  • Hex_Digit: the hexadecimal digits 0-9, a-f, and A-F.
  • …and many more.

Unicode defines a long list of properties. Rather than reproduce it all, here are the reference sources:

Example: hexadecimal numbers

Say you want to find hex bytes written like xE0, where each character after the x is a hex digit (09 or AF).

The pattern:\p{Hex_Digit} property matches exactly those digits:

let regexp = /x\p{Hex_Digit}\p{Hex_Digit}/u;

alert("byte: xC3".match(regexp)); // xC3

Two pattern:\p{Hex_Digit} in a row require two hex digits after the x. Because the property already knows the whole hex-digit set, you avoid spelling out a pattern:[0-9a-fA-F] character range by hand.

Example: Chinese characters

To search by writing system, Unicode has the Script property. Its values name scripts: Cyrillic, Greek, Arabic, Han (used for Chinese), and so on — here’s the full list.

You match a script with pattern:\p{Script=<value>}, or the short form pattern:\p{sc=<value>}. Cyrillic letters would be pattern:\p{sc=Cyrillic}; Chinese characters are pattern:\p{sc=Han}:

let regexp = /\p{sc=Han}/gu; // matches Han (Chinese) characters

let str = `Ciao Спасибо 山川 42_88`;

alert( str.match(regexp) ); // 山,川

The Latin Ciao, the Cyrillic Спасибо, and the digits are all skipped. Only the two Han characters and come back.

Ciao
Latin
Спасибо
Cyrillic
山 川
Han → matched
42_88
digits
\p{sc=Han} keeps only characters whose Script property is Han; everything else is filtered out.

Example: currency

Currency signs like , £, and share the property pattern:\p{Currency_Symbol}, aliased to pattern:\p{Sc}.

Combine it with pattern:\d to find prices in a “currency then a digit” shape:

let regexp = /\p{Sc}\d/gu;

let str = `Prices: €3, £5, ₹8`;

alert( str.match(regexp) ); // €3,£5,₹8

Each match is a currency symbol immediately followed by one digit. Matching multi-digit numbers needs quantifiers, which the Quantifiers +, *, ? and {n} article covers.

Here pattern:\p{Sc}\p{Nd}+ finds a currency symbol followed by one or more decimal digits, so it captures whole prices. Edit the text and add prices in any currency — the property class recognizes every symbol without you listing them:

interactiveMatch prices in any currency

Summary

The pattern:u flag turns on Unicode support in a regular expression, and that buys you two things:

  1. Correct 4-byte handling. A surrogate pair is matched as one character instead of two 2-byte halves.
  2. Property classes. You can search by Unicode property with pattern:\p{…}.

With property classes you can target letters of a specific language, digits, punctuation, quotes, currency symbols, and much more — declaratively, without maintaining fragile hand-written character ranges.