Why LEN seems wrong sometimes

An introduction to Unicode, for spreadsheet people

Type one emoji into a cell and ask Excel how long it is:

That’s one picture, and Excel says it’s eight characters long, or five in newer workbooks. Neither answer is 1. It isn’t a bug. Excel is counting things you can’t see, and which things it counts depends on a workbook setting.

This page explains what those things are, from the ground up, and assumes you know nothing about Unicode yet. By the end you’ll be able to explain both numbers, and why two pieces of text that look exactly alike can refuse to match.

  • How text is storedCharacters are numbers, and one character can take up several of the “slots” that Excel counts.
  • Why LEN surprises youExcel counts storage slots or Unicode characters, never quite the characters you see.
  • Why your Excel version mattersA per-workbook setting changes what LEN, MID, FIND, SEARCH and REPLACE count.
  • Why = and EXACT disagreeExcel has two definitions of “equal,” and invisible characters fall between them.

Already know this and just need to debug something? Go to the workbench or the cheat sheet.

Unicode from zero

Computers can only store numbers. Text works because everyone agrees on which number stands for which character.

That agreement is called a character set. One of the earliest was ASCII (1963). It covered 128 characters: English letters, digits and punctuation. In ASCII, A is 65, B is 66 and a space is 32. That was enough for English and nothing else. There was no é, no Ω and no .

Other countries built their own sets, and they reused the same numbers for different characters. A file written with one set and opened with another came out as garbage, which is why you sometimes still see café where café should be.

Unicode (1991) fixed this by giving every character in every writing system its own number, plus symbols, math and eventually emoji. That number is the character’s code point.

Reading U+ notation

Code points are written as U+ followed by the number in hexadecimal (base 16). Hex uses sixteen digits, 09 and then AF, so A means 10 and F means 15.

hex → decimal41 = 4×16 + 1 = 65
hex → decimalFF = 15×16 + 15 = 255
largest 4-digit hexFFFF = 65,535
largest code point10FFFF = 1,114,111

Why hex and not ordinary decimal? Each hex digit fits exactly into 4 bits, so hex lines up neatly with how computers store data. Four hex digits are exactly 16 bits. That fact explains the LEN puzzle, so keep it in mind.

CharacterCode pointDecimalWhat it is
AU+004165Same as ASCII. Unicode kept the first 128.
éU+00E9233Latin small letter e with acute
U+26029730Umbrella
👨U+1F468128104Man. Five hex digits.
ZWJU+200D8205Zero width joiner. Invisible, but it’s a real character.

Seeing code points in Excel

Two functions convert between characters and their code points, and two more convert between decimal and hex:

=UNICODE("A")            → 65       character → code point (decimal)
=UNICHAR(9730)           → ☂        code point → character
=DEC2HEX(9730)           → 2602     decimal → hex, so this is U+2602
=UNICHAR(HEX2DEC("1F468")) → 👨      from U+ notation back to the character
Key idea

A code point is a character’s ID number. It doesn’t decide how the character looks (the font does that) or how many slots it takes up in memory (the encoding does that). Those are separate questions.

Why LEN says 8 (or 5)

Back to the family emoji. We’ll count it three times, looking a little deeper into how it’s stored each time. Both of Excel’s answers will turn up along the way.

1

What you see 1

One picture of a family. It’s what you’d point at with your finger and call one character.

👨‍👩‍👧
1 picture
2

What Unicode sees 5

Unicode has no single “family” code point for this. The picture is built from three people joined by two invisible glue characters called zero-width joiners (ZWJ). Your phone or computer sees man + glue + woman + glue + girl and draws one combined picture.

👨
U+1F468
ZWJ
U+200D
👩
U+1F469
ZWJ
U+200D
👧
U+1F467

This is what LEN reports in Compatibility Version 2 workbooks.

3

How the text is stored 8

Excel stores text in UTF-16, which means every character goes into fixed 16-bit slots called code units. Each slot holds exactly four hex digits, up to FFFF. The glue fits in one slot because 200D is four digits. Each person is five digits (1F468) and doesn’t fit, so it’s split across two slots.

👨
D83DDC68
2 slots
ZWJ
200D
1 slot
👩
D83DDC69
2 slots
ZWJ
200D
1 slot
👧
D83DDC67
2 slots
2 + 1 + 2 + 1 + 2 = 8  ← what LEN reports in Compatibility Version 1

Neither answer is wrong. They answer different questions. In Version 1, LEN answers “how many 16-bit slots does this take up?” and says 8. In Version 2, it answers “how many Unicode characters is this?” and says 5. Neither counts what you see. The split into two slots is called a surrogate pair (section 5). Which count your workbook uses depends on its compatibility version (section 4).

Check each stage yourself:

FormulaVersion 1Version 2
=LEN(UNICHAR(128104))21
=LEN(UNICHAR(8205))11
=LEN(UNICHAR(128104)&UNICHAR(8205)&UNICHAR(128105)&UNICHAR(8205)&UNICHAR(128103))85

Three meanings of “character”

The three counts you just saw have names. Most text bugs come from counting one of them while thinking about another.

NamePlain EnglishFamily emojiExcel counts it with
GraphemeWhat a person sees and would call one character.1REGEXEXTRACT + \X (§7)
Code pointOne of Unicode’s numbered characters. A grapheme can be made of several.5LEN in Version 2
Code unitOne storage slot. In UTF-16 a slot is 16 bits, and a code point takes one or two.8LEN in Version 1
Think of typing é on a keyboard that has no é key: you press the accent key, then e. That’s two keystrokes for one letter on screen. Code points are like the keystrokes and graphemes are like the letters you end up with. Code units go one level lower still: they’re how much space those keystrokes take up in memory.
The rule

No version of LEN counts graphemes. In Version 1 workbooks, LEN, MID, FIND, SEARCH and REPLACE count code units. In Version 2 they count code points. Either way, invisible marks, skin tones and joiners all add to the count.

Here are more examples. Each one is a single grapheme, and they don’t all look like emoji:

Looks likeMade ofGraphemesLEN v2
code points
LEN v1
code units
Aa plain letter111
umbrella111
☂️umbrella + invisible “draw as emoji” marker122
🐸frog, a five-digit code point112
e + a combining accent122
👋🏽waving hand + skin tone124
🇯🇵two “regional indicator” letters J + P = flag of Japan124
👨‍👩‍👧three people + two joiners158

On Windows the flag shows as the letters “JP”, because Windows doesn’t include flag emoji. The data is fine; it’s still one grapheme.

Compatibility versions

For decades, LEN("😀") returned 2. Microsoft has now fixed that. To avoid silently changing the results of millions of existing spreadsheets, the fix is a per-workbook setting.

A compatibility version is a workbook setting that decides which behaviour certain functions use. There are currently two:

Version 1

Legacy

  • Text functions count UTF-16 code units, so an emoji outside the BMP counts as 2
  • Every Excel supports it
  • Workbooks created before the change open as Version 1
Version 2

Unicode-aware

  • Text functions count code points, so a surrogate pair counts as 1
  • Excel for Microsoft 365 (Windows and Mac) only
  • The default for new workbooks in the Current Channel since April 2026

What changes

Microsoft lists exactly five functions: LEN, MID, FIND, SEARCH and REPLACE. In Version 2 they treat a surrogate pair as one character, so lengths and positions are counted in code points.

TextFormulaVersion 1Version 2
🐸=LEN(UNICHAR(128056))21
👨‍👩‍👧LEN of the family85
👋🏽=LEN(UNICHAR(128075)&UNICHAR(127997))42
☂️=LEN(UNICHAR(9730)&UNICHAR(65039))22
🐸=MID(UNICHAR(128056), 1, 1)half a frog (�)the whole frog

What doesn’t change

  • Version 2 still doesn’t count graphemes. It counts code points. Microsoft specifically notes that variation selectors are still counted separately, and so are skin tones and ZWJ joiners, which is why the family is still 5. For a grapheme count, see section 7.
  • Other text functions aren’t on the list. Coverage of the change describes LEFT, RIGHT, TEXTBEFORE, TEXTAFTER and TEXTSPLIT as already handling surrogate pairs correctly. Comparisons (=, EXACT) and SUBSTITUTE aren’t mentioned either, so section 10 applies in both versions.

Checking and changing it

Go to Formulas › Calculation Options › Compatibility Version. The setting belongs to the workbook, not to your copy of Excel. Two workbooks open side by side can give different LEN results for the same text.

There’s no function that reports the version directly, but the frog gives it away. A single emoji is 2 slots in Version 1 and 1 code point in Version 2:

=IF(LEN(UNICHAR(128056)) = 1, "Version 2", "Version 1")
Sharing workbooks

Excel 2024 and earlier only support Version 1. If someone opens a Version 2 workbook in one of them, they get a warning that calculation results may differ. The five functions then fall back to counting code units. A formula like =MID(A1, 3, 1) can return different text for the same cell depending on who opens the file.

The rollout details are recent and may change. Microsoft’s Compatibility Versions page has the latest.

Workbench

Now that you know the three counts, try your own text. Paste anything below, such as a cell’s contents, a name that won’t match, or an emoji. Everything runs in your browser and nothing is sent anywhere.

How to read the inspector
  • The four numbers are the counts from section 3. Code points are what LEN returns in Version 2 workbooks; UTF-16 units are what it returns in Version 1.
  • The alignment strip stacks the three layers so you can see which slots belong to which code point, and which code points belong to which grapheme. Purple and teal slots are the two halves of a surrogate pair.
  • Orange dashed boxes are invisible characters or characters that attach to the one before them. These are the usual suspects when text won’t match.
  • Click a row to see each code point’s official name and number. “Break role” says whether it starts a new grapheme (Base) or attaches to the previous one (Extend, ZWJ).
  • The formula at the bottom rebuilds your exact string in Excel, invisible characters included.

Inspect a string

One row per grapheme; click a row to see its code points.
0
Graphemes
what a person sees · \X count
0
Code points
= LEN in Version 2
0
UTF-16 units
= LEN in Version 1
0
UTF-8 bytes
size in a CSV file

Are these equal?

Type two strings. Each box answers “are they equal?” by a different rule. The first works like Excel’s EXACT() and the last models Excel’s =. Section 10 explains why they differ.
A
B

Surrogate pairs

This section explains how a code point too big for one slot gets split into two. It’s the reason for Version 1’s 8, and the thing Version 2 was built to hide.

A little history

Early Unicode assumed 65,536 characters (0000FFFF) would be enough for everything. Windows, Java, JavaScript and Excel were designed around that, with one 16-bit slot per character. In 1996 Unicode grew past that limit to make room for historic scripts, rare Chinese characters and, later, emoji. The new maximum was 10FFFF.

Those systems couldn’t switch to bigger slots without breaking everything already written. So Unicode set aside 2,048 values in the 16-bit range that aren’t characters at all. Each one means “I’m half of a bigger number.” These are surrogates:

  • A high surrogate (D800DBFF) always comes first and carries the top half of the number.
  • A low surrogate (DC00DFFF) always comes second and carries the bottom half.

Together they make a surrogate pair: two slots that represent one code point. The two ranges don’t overlap, so any slot tells you whether it’s a first half, a second half, or a normal character.

It works like writing a long number across two boxes on a paper form, where the boxes are different colours so nobody mixes up which half is which.

The code space is divided into 17 planes of 65,536 code points each. Plane 0, the Basic Multilingual Plane, covers everything up to FFFF. That includes almost all everyday text in most languages, and it fits in one slot. Planes 1–16, sometimes called the “astral” planes, need a pair. Most emoji live in plane 1, which is why a single emoji has a LEN of 2 in Version 1 workbooks.

Worked example: 🐸 U+1F438 frog face

1F438 is bigger than FFFF, so it needs two slots. The recipe is: subtract 10000, split what’s left into two 10-bit halves, and add each half to the start of its surrogate range.

code point0x1F438  = 128,056
− 0x100000xF438  (now fits in 20 bits)
20 bits, split 10/100000111101 0000111000
top half + 0xD8000x3D + 0xD800 = D83D
bottom half + 0xDC000x38 + 0xDC00 = DC38
stored asD83D DC38  → 2 slots
Going backwards: pair → code point
cp = (hi − 0xD800) × 0x400 + (lo − 0xDC00) + 0x10000
   = (0xD83D − 0xD800) × 0x400 + (0xDC38 − 0xDC00) + 0x10000
   = 0x3D × 0x400 + 0x38 + 0x10000
   = 0x1F438  ✓

0x400 is 1,024 = 210, the size of each half. The 0x prefix just means “this number is hex.”

It’s still one code point. The pair only exists in UTF-16. Other encodings store the same frog differently:

UTF-8F0 9F 90 B84 bytes. Used by the web and most CSV files.
UTF-16D83D DC382 × 16-bit slots. Used by Excel, Windows and JavaScript.
UTF-320001F4381 × 32-bit slot. Simple, but uses a lot of space.
Where this bites (Version 1 workbooks)

MID counts slots, so it can cut a pair in half. =MID(UNICHAR(128056), 1, 1) returns only the high surrogate, which displays as � or an empty box. Passing that half to UNICODE() gives #VALUE!, because half a pair isn’t a character. FIND and SEARCH positions are also counted in slots, so a position found in one version can point at the wrong character in the other.

In Version 2, MID counts code points and returns the whole frog. UNICODE works in both versions when you give it a complete pair: =UNICODE("🐸") returns 128056.

Grapheme clusters

A grapheme is what a reader sees as one character. Unicode’s name for it is an extended grapheme cluster: a base character plus everything attached to it.

Some code points never stand alone. They modify the character in front of them, like an accent placed on a letter. When text is split into graphemes, these attaching characters stay with the base:

U+0065+U+0301
The letter e, then a combining acute accent that sits on top of it.
👋🏽
U+1F44B+U+1F3FD
A waving hand, then a skin-tone modifier. On its own the modifier is a colour swatch.
👨‍👩‍👧
1F468200D1F469200D1F467
People joined by zero-width joiners (section 8).

Unicode doesn’t keep a list of every possible grapheme, because there are far too many combinations. Instead it gives every code point a property that says how it behaves: “I’m a normal character,” “I attach to the previous one,” “I’m a joiner,” “I’m half a flag,” and so on. A short set of rules in a document called UAX #29 uses those properties to decide where one grapheme ends and the next begins. In short:

  • Accents and modifiers attach. Combining marks, skin tones and the invisible emoji-style marker U+FE0F stay with the character before them.
  • Joined emoji stay together. Emoji + ZWJ + emoji forms one grapheme.
  • Flag letters pair up. Regional-indicator letters join two at a time, so 🇯🇵🇫🇷 is two flags (Japan and France) and not one four-letter blob.
  • Everything else is its own grapheme.
The actual rules (optional)

× means “don’t break here” and ÷ means “break here.” The names (Extend, ZWJ, RI…) are values of each code point’s Grapheme_Cluster_Break property.

RulePatternMeaning
GB3CR × LFA Windows line break (\r\n) is one grapheme.
GB9× (Extend | ZWJ)Never break before an accent, U+FE0F, a skin tone or a ZWJ.
GB9a/b× SpacingMark, Prepend ×Keeps vowel signs attached in scripts such as Hindi and Thai.
GB11ExtPict Extend* ZWJ × ExtPictEmoji joined with a ZWJ stay together.
GB12/13RI RI × RI RIFlag letters pair up two at a time.
GB999Any ÷ AnyOtherwise, break.

No version of LEN follows these rules. Excel does have a way to split text into graphemes, though, and it’s covered in the next section.

Counting graphemes with \X

Excel for Microsoft 365 has three regular expression functions: REGEXTEST, REGEXEXTRACT and REGEXREPLACE. Their regex language includes a pattern that matches exactly one grapheme.

A regular expression (regex) is a small pattern language for describing text. Excel’s regex functions use a variety called PCRE2, where \X (capital X) means “one extended grapheme cluster.” That’s the same unit the grapheme rules in section 6 produce: what a reader sees as one character.

REGEXEXTRACT(text, pattern, [return_mode]) returns the text that matches the pattern. With return_mode set to 1 it returns every match as an array that spills into neighbouring cells. Using \X as the pattern returns one cell per grapheme:

A1:  👨‍👩‍👧🐸é

=REGEXEXTRACT(A1, "\X", 1)        → 👨‍👩‍👧 | 🐸 | é     (spills 3 cells)

Now you can do what LEN never could:

GoalFormula
Count graphemes=IF(A1="", 0, COUNTA(REGEXEXTRACT(A1, "\X", 1)))
First grapheme (a safe LEFT(A1,1))=REGEXEXTRACT(A1, "\X")
First n graphemes=CONCAT(TAKE(TOCOL(REGEXEXTRACT(A1, "\X", 1)), n))
Size of each grapheme=LEN(REGEXEXTRACT(A1, "\X", 1))
Every code point number=UNICODE(REGEXEXTRACT(A1, "(?s).", 1))
  • The IF guard. When nothing matches (an empty cell), REGEXEXTRACT returns an error, and COUNTA would count that error as 1.
  • TOCOL makes the spill a single column whichever direction it comes out, so TAKE always takes the first n items.
  • "(?s).": a . matches one code point, never half a surrogate pair, so this lists code points the same way in Version 1 and Version 2. (?s) lets . match line breaks too.
  • No escaping needed. Backslashes are ordinary characters in Excel strings, so type "\X" exactly as shown. Only double quotes need doubling.

The family emoji, all three ways:

FormulaCountsResult
=LEN(A1)code units (Version 1)8
=LEN(A1)code points (Version 2)5
=COUNTA(REGEXEXTRACT(A1, "\X", 1))graphemes (either version)1
Caveats

• The regex functions are only in Excel for Microsoft 365 (Windows and Mac). Older versions show #NAME?.

• The grapheme rules come from the regex engine, which follows a particular Unicode version. Very new emoji may split differently than in your browser, so if a count looks wrong, compare it with the workbench.

\X uses the grapheme rules, not the list of approved emoji. An unapproved combination like frog + ZWJ + umbrella counts as 1 even though it displays as two pictures (section 8).

Regex is also a compact way to delete invisible characters. This removes all 16 variation selectors in one go:

=REGEXREPLACE(A1, "[\x{FE00}-\x{FE0F}]", "")

\x{FE0F} is PCRE2’s way to write a code point in hex, and […-…] matches anything in that range.

The zero-width joiner U+200D

The ZWJ is the invisible glue from the family emoji. On its own it has no width and no shape. Put it between two emoji and it asks the font to draw them as one combined picture.

👩‍💻
1F469200D1F4BB
woman + ZWJ + laptop = woman technologist. LEN 5 (v1) or 3 (v2).
❤️‍🔥
2764FE0F200D1F525
heart + emoji-style marker + ZWJ + fire = heart on fire. LEN 5 (v1) or 4 (v2).
🐸‍☂️
1F438200D2602FE0F
frog + ZWJ + umbrella. No such emoji exists, so you see two pictures.
The rule

Joined emoji only merge into one picture when Unicode has approved that exact combination and your font includes a drawing of it. You can’t make a new emoji by putting a ZWJ between any two.

The approved combinations are listed in a file called emoji-zwj-sequences.txt, and the set is called RGI (“recommended for general interchange”). If a combination isn’t on the list, or your font doesn’t have it, the pieces are drawn side by side.

There’s a wrinkle. The grapheme rules only look at properties (“this is an emoji, this is a ZWJ”) and never check the approved list. So the frog-umbrella counts as one grapheme in the workbench and with \X, even though you see two pictures. Most of the time, what you see and the grapheme count agree. When they don’t, the grapheme count follows the rules and the picture follows the font.

The ZWJ also explains a common cleanup bug. If you delete all the emoji from some text, the invisible ZWJs that sat between them are still there, making LEN higher than it should be.

Text vs emoji style

Many symbols can be drawn two ways: a plain black-and-white symbol (“text style”) or a colourful emoji (“emoji style”). It’s the same code point either way.

Each symbol has a default style. Older symbols from before emoji existed, like the umbrella, usually default to text style. Newer ones usually default to emoji style. To override the default, you add an invisible variation selector right after the symbol:

  • U+FE0F (“VS16”) means draw it as an emoji.
  • U+FE0E (“VS15”) means draw it as text.
RenderedCode pointsStyleWhy
2602textUmbrella defaults to text style
☂️2602FE0FemojiVS16 asks for emoji
2614emojiUmbrella with rain drops defaults to emoji style
26F5emojiSailboat defaults to emoji style
⛵︎26F5FE0EtextVS15 asks for text (not every font supports this)

The default is recorded in a Unicode property called Emoji_Presentation. Your screen may not match the table: many fonts, including Windows’ emoji font, draw some symbols in colour whatever the selector says. That makes the problem worse, because two strings with different code points end up looking identical.

Why this matters

Emoji keyboards and pickers usually insert the VS16 version so you get colour. Typed text, older data and some programs leave it out. The same symbol ends up stored two ways depending on where it came from, and both versions look alike on screen. and ☂️ have different lengths in both compatibility versions and don’t pass EXACT. That’s the classic “identical text won’t match” problem, and the next section is about how to deal with it.

Excel’s two kinds of equal

Excel has two ways to check whether text matches, and they don’t agree. Most “identical text won’t match” problems come from using both in the same workbook.

Loose

A1 = B1

  • Ignores the invisible emoji marker U+FE0F: = ☂️
  • Treats both spellings of an accented letter as equal: é (one code point) = (e + accent)
  • Ignores upper and lower case: a = A
Strict

EXACT(A1, B1)

  • Compares the text code point by code point
  • Case matters
  • Treats all of the pairs on the left as different

These functions are strict like EXACT: SUBSTITUTE, FIND and EXACT itself. They look for the exact same code points. Compatibility versions change how FIND counts positions, not what it considers a match.

Paste these into a sheet to see the difference:

PairFormula=EXACT
☂ vs ☂️=UNICHAR(9730) = UNICHAR(9730)&UNICHAR(65039)TRUEFALSE
é vs é=UNICHAR(233) = "e"&UNICHAR(769)TRUEFALSE
a vs A="a" = "A"TRUEFALSE

For the EXACT column, wrap the same two values: =EXACT(UNICHAR(9730), UNICHAR(9730)&UNICHAR(65039)). The = results were observed in Excel for Microsoft 365, so try them on your own version to confirm.

Why mixing them causes trouble

Say A1 holds the plain umbrella and B1 holds the emoji-style one. A formula that checks with = and then acts with SUBSTITUTE gets two different answers:

Step 1 · Check

=IF(A1 = B1, …)
↓ loose: ignores the marker
TRUE — “they match”

Step 2 · Act

=SUBSTITUTE(A1, B1, "")
↓ strict: needs an exact copy
no exact copy — nothing removed
A1: =REPT(UNICHAR(9730), 2)            ☂☂  (plain)
B1: =UNICHAR(9730) & UNICHAR(65039)   ☂️  (with marker)
=A1 = REPT(B1, 2)                      → TRUE
=SUBSTITUTE(A1, B1, "")                → ☂☂   unchanged
The other direction is worse

Swap them, so the text holds ☂️ and you remove the plain . SUBSTITUTE does find it inside the longer text and deletes it, leaving the invisible marker U+FE0F behind. You can’t see it, but LEN counts it, and every later comparison with that cell fails.

The fix

Use one definition of “equal” throughout. Either clean the data once so every copy is stored the same way, or clean both sides right before comparing (section 11 shows how). For emoji-style markers, removing every VS16 is often enough:

=SUBSTITUTE(A1, UNICHAR(65039), "")

More Excel gotchas

  • Excel has no function to clean up Unicode. = does some of this cleanup internally for its comparison, but you can’t get the cleaned text back out.
  • DEC2HEX(cp, 4) returns #NUM! for emoji. The optional 4 means “pad to 4 digits,” and a code point like 1F438 needs 5, the same 4-digit limit from section 2. Leave the 4 out: DEC2HEX(cp).
  • CODE and CHAR predate Unicode and only know an old Windows character set. For anything outside that set, CODE returns 63, the code for ?. Use UNICODE and UNICHAR instead.
  • SEARCH ignores case and FIND doesn’t. XLOOKUP and MATCH ignore case too. For a lookup that respects case, use =XLOOKUP(TRUE, EXACT(keys, x), values).
  • The usual way to count occurrences, (LEN(s) − LEN(SUBSTITUTE(s, x, ""))) / LEN(x), gives wrong answers when some copies of x have the FE0F marker and others don’t. Count them with COUNTA(REGEXEXTRACT(s, …, 1)) after cleaning instead.

Normalization: making equal text look equal

To normalize text is to rewrite it into one standard form, so that text that means the same thing is also stored the same way. Then even strict comparisons like EXACT agree.

There are two kinds of cleanup, and they need very different tools. Some you can work out from the code point’s number alone. The rest needs a lookup table.

Kind 1: the number tells you (a formula can do it)

A few kinds of character can be spotted by their number because they sit in known ranges. For comparison purposes you can usually just delete them:

DeleteRangeDecimal (for UNICHAR)
Variation selectors (the emoji/text markers)U+FE00 – U+FE0F65024 – 65039
Skin-tone modifiersU+1F3FB – U+1F3FF127995 – 127999
Zero-width joinerU+200D8205

With the regex functions it’s one line, because a character class can hold several ranges:

=REGEXREPLACE(A1, "[\x{FE00}-\x{FE0F}\x{1F3FB}-\x{1F3FF}\x{200D}]", "")

Without them (any Excel 365), loop over the list of numbers and remove each character in turn:

=LET(
  strip, VSTACK(SEQUENCE(16,,65024), 8205, SEQUENCE(5,,127995)),
  REDUCE(A1, strip, LAMBDA(s, c, SUBSTITUTE(s, UNICHAR(c), "")))
)

Save either one as a named LAMBDA (for example FOLDKEY) and compare FOLDKEY(A1) = FOLDKEY(B1). Use it only for comparing. Removing the ZWJ turns 👩‍💻 into 👩💻, which is not something you want to store.

Kind 2: needs a lookup table (a formula can’t)

Remember the two ways to write é? Unicode says they’re canonically equivalent, which means officially the same text:

  • Composed: one code point, U+00E9 (233).
  • Decomposed: two code points, U+0065 e (101) + U+0301 combining accent (769).

You can’t get from 233 to “101 + 769” with arithmetic. The pairing was simply decided by the Unicode Consortium and recorded in a table (UnicodeData.txt). Some pairings are even odder: U+212B ANGSTROM SIGN is officially the same as Å U+00C5. The standard forms have names:

  • NFC (“composed”) turns everything into the one-code-point form where one exists. It’s the most common choice.
  • NFD (“decomposed”) splits everything into base + accents.
The rule

A formula alone can’t convert text to NFC or NFD, because that needs Unicode’s table. Excel’s = uses the table internally, but only to compare, and never gives you the converted text.

One fun exception: the 11,172 Korean Hangul syllables are arithmetic. Each syllable’s number encodes its parts, and the workbench uses that to name them. Try 한 (U+D55C, “HAN”).

Where to do it instead

Normalize the text before it reaches the grid, or with a tool that has the table built in:

// JavaScript
"e\u0301".normalize("NFC") === "\u00E9"     // true

# Python
unicodedata.normalize("NFC", s)

' VBA: call the Windows API
Declare PtrSafe Function NormalizeString Lib "kernel32" ( _
    ByVal NormForm As Long, ByVal lpSrc As LongPtr, ByVal cwSrc As Long, _
    ByVal lpDst As LongPtr, ByVal cwDst As Long) As Long   ' NormForm 1 = NFC

How many characters are there, really?

Unicode has room for 1,114,112 code points (17 planes × 65,536). Most of that room is empty.

SliceCountWhat it is
Total code space1,114,112Every number from U+0000 to U+10FFFF
Characters in Unicode 16.0 (2024)154,998Actual characters with names: letters, symbols, emoji…
Characters in Unicode 17.0 (2025)159,8014,803 added
Surrogates2,048The “half of a pair” values from section 5. They exist only to make UTF-16 work and are never characters.
Private use137,468Left empty on purpose so companies and apps can put their own symbols there (icon fonts, for example)
Noncharacters66Permanently reserved for programs’ internal use

So about 86% of the code space holds no standard character (1 − 154,998 / 1,114,112). If you don’t count the reserved areas (surrogates, private use and noncharacters), about 73% is still completely unassigned. There’s plenty of room for new emoji.

Watch the definitions

You’ll sometimes see ~297,000 quoted for Unicode 17.0. That figure counts the 137,468 private-use slots as assigned (159,801 + 137,468 = 297,269), so you can’t compare it with the ~155,000 figure for 16.0. Counted the same way, 17.0 added about 4,800 characters.

Reserved code points don’t have names. Tools show placeholders like <private-use-E000> instead.

The official character list, UnicodeData.txt, has far fewer lines than there are characters. Large blocks whose names follow a pattern are stored as just a first and a last line. For example, all 42,720 characters of CJK Extension B (200002A6DF) take two lines:

20000;<CJK Ideograph Extension B, First>;Lo;0;L;;;;;N;;;;;
2A6DF;<CJK Ideograph Extension B, Last>;Lo;0;L;;;;;N;;;;;

Cheat sheet

For when you already know the concepts and just need the fix. The section numbers point back to the explanations.

SymptomLikely causeFix
Identical-looking text won’t matchInvisible U+FE0F or U+FE0E on one side (§9)Remove variation selectors from both sides (§11), or store one consistent form
= says TRUE but SUBSTITUTE/FIND find nothing= is loose and those functions are strict (§10)Clean both sides before substituting, and test with EXACT
EXACT is FALSE for “the same” accented wordOne side stores the accent separately, as e + U+0301 (§11)Normalize to NFC before the data reaches Excel
Same text, different LEN in two workbooksThe workbooks use different compatibility versions (§4)Formulas › Calculation Options › Compatibility Version
LEN says 2 for one emojiVersion 1 workbook: surrogate pair counted as 2 slots (§5)Switch to Version 2, or count graphemes with \X (§7)
LEN says 2+ for one emoji even in Version 2Skin tone, ZWJ joins or VS16 markers are separate code points (§6, §8)Count with COUNTA(REGEXEXTRACT(A1,"\X",1))
LEN is one more than expectedA leftover U+FE0F from an earlier substitution (§10)Remove UNICHAR(65039)
Counting occurrences with SUBSTITUTE gives the wrong numberOnly some copies have FE0F, or the search text has several partsClean both strings first, then count
MID shows � or a boxVersion 1: cut a surrogate pair in half (§5)Use Version 2, or take whole graphemes with REGEXEXTRACT (§7)
UNICODE() returns #VALUE!It was given half a surrogate pair (§5)Fix the cut that produced it
FIND position is off after sharing the filePositions count slots in Version 1 and code points in Version 2 (§4)Don’t store positions; recompute them in the workbook that uses them
DEC2HEX(cp,4) returns #NUM!The code point needs 5 hex digits (§10)Leave out the 4
CODE() returns 63The character isn’t in the old Windows character setUse UNICODE()
Emoji shows black-and-whiteText-style default and no VS16 (§9)Append UNICHAR(65039), and do it the same way everywhere
Two emoji appear where one shouldUnapproved ZWJ combination, or the font doesn’t have it (§8)Check emoji-zwj-sequences.txt
Flag shows as two lettersWindows has no flag emojiNot a data problem; the text is correct
Lookup ignores case when you need it to match exactlyXLOOKUP/MATCH ignore caseXLOOKUP(TRUE, EXACT(keys, x), vals)

Handy numbers for UNICHAR: 65039 VS16 (emoji style) · 65038 VS15 (text style) · 8205 ZWJ · 769 combining acute accent · 127995–127999 skin tones. Version check: =IF(LEN(UNICHAR(128056))=1, "Version 2", "Version 1").

Glossary

ASCII
A 1963 character set of 128 characters: English letters, digits and punctuation. Unicode’s first 128 code points are the same.
Bit
A single 0 or 1. 16 bits can represent 216 = 65,536 different values. One hex digit is exactly 4 bits.
BMP (Basic Multilingual Plane)
Plane 0, U+0000–U+FFFF. Everything here fits in one UTF-16 slot.
Canonical equivalence
Unicode’s official statement that two different code point sequences mean the same text, such as é and e + U+0301.
Code point
A character’s number in Unicode, written U+ and hex, e.g. U+2602. Ranges from U+0000 to U+10FFFF. LEN counts these in Version 2 workbooks.
Code unit
One storage slot in an encoding. In UTF-16 a slot is 16 bits. LEN counts these in Version 1 workbooks.
Combining mark
A code point that attaches to the character before it, such as an accent. It has no meaning on its own.
Compatibility version
A per-workbook Excel setting (Formulas › Calculation Options) that picks old or new behaviour for certain functions. In Version 2, LEN, MID, FIND, SEARCH and REPLACE count code points instead of code units.
Encoding
The method for storing code points as bytes. UTF-8, UTF-16 and UTF-32 all store the same code points in different ways.
Grapheme (cluster)
What a reader sees as one character. It can be made of many code points. In Excel, REGEXEXTRACT with \X finds them.
Hexadecimal (hex)
Base-16 numbers using the digits 0–9 and A–F. Written with a 0x or U+ prefix. Excel converts with DEC2HEX and HEX2DEC.
NFC / NFD
Standard “normal forms” of text. NFC uses the one-code-point form where possible and NFD splits into base + marks. Converting to one is called normalizing.
PCRE2
“Perl Compatible Regular Expressions,” version 2. The regex engine behind Excel’s REGEX functions. In PCRE2, \X matches one grapheme.
Plane
A block of 65,536 code points. There are 17 of them (0–16).
Regular expression (regex)
A pattern language for matching text. For example, \d+ means “one or more digits.”
RGI
“Recommended for general interchange”: Unicode’s list of emoji and emoji combinations that platforms are expected to support.
Surrogate pair
Two UTF-16 slots (high D800–DBFF, then low DC00–DFFF) that together store one code point above U+FFFF.
UAX #29
The Unicode document that defines how to split text into graphemes, words and sentences.
UTF-16
An encoding that uses 16-bit slots: one slot for code points up to U+FFFF, two (a surrogate pair) above that. Used by Excel, Windows, Java and JavaScript.
Variation selector
An invisible code point that picks a drawing style for the character before it. U+FE0F = emoji style, U+FE0E = text style.
ZWJ (zero-width joiner)
U+200D. Invisible glue that asks the font to combine the emoji on either side into one picture.

References

Notes

  1. Microsoft’s own pages list only the five functions that change. The statement that LEFT, RIGHT, TEXTBEFORE, TEXTAFTER and TEXTSPLIT already handled surrogate pairs comes from FM Magazine’s coverage.
  2. Microsoft’s REGEXEXTRACT documentation doesn’t say what is returned when nothing matches, or which direction the results spill. The IF and TOCOL wrappers guard against both.
  3. Microsoft doesn’t document that = ignores U+FE0F and treats decomposed accents as equal. These results come from testing in Excel for Microsoft 365 and could change.

Citations

  1. “ASCII”. Wikipedia.
  2. “Mojibake”. Wikipedia.
  3. “Unicode”. Wikipedia.
  4. “Compatibility Versions”. Microsoft Support. Microsoft.
  5. “LEN function”. Microsoft Support. Microsoft.
  6. “Improving five Excel text functions: LEN, MID, SEARCH, FIND, and REPLACE + Compatibility Versions”. Microsoft 365 Insider Blog. Microsoft Tech Community.
  7. “Compatibility Versions in Excel: What you need to know”. FM Magazine. November 2025.
  8. “UTF-16”. Wikipedia.
  9. “FAQ: UTF-8, UTF-16, UTF-32 & BOM”. The Unicode Consortium.
  10. “Unicode Standard Annex #29: Unicode Text Segmentation”. The Unicode Consortium.
  11. “REGEXEXTRACT function”. Microsoft Support. Microsoft.
  12. “pcre2pattern: Perl-compatible regular expression syntax”. PCRE2 documentation.
  13. “emoji-zwj-sequences.txt”. The Unicode Consortium.
  14. “Unicode Technical Standard #51: Unicode Emoji”. The Unicode Consortium.
  15. “Unicode Standard Annex #15: Unicode Normalization Forms”. The Unicode Consortium.
  16. “UnicodeData.txt”. Unicode Character Database. The Unicode Consortium.
  17. “NormalizeString function (winnls.h)”. Microsoft Learn. Microsoft.
  18. “Unicode 16.0.0”. The Unicode Consortium. September 2024. “Unicode 16.0 adds 5185 characters, for a total of 154,998 characters.”
  19. “Unicode 17.0.0”. The Unicode Consortium. September 2025. “Unicode 17.0 adds 4803 characters, for a total of 159,801 characters.”
  20. “FAQ: Private-Use Characters, Noncharacters & Sentinels”. The Unicode Consortium.
  21. “Intl.Segmenter”. MDN Web Docs. Mozilla.

Cite this page

Freelove, Jeremy (2026). “Why LEN seems wrong sometimes: An introduction to Unicode, for spreadsheet people”. jeremyfreelove.com. Updated 18 September 2026.

Links point to the latest version of each page. Microsoft’s pages on Compatibility Versions describe a feature that is still rolling out, so check them again if something here doesn’t match your Excel.