By Docify

UTF-16 vs Locale Sort: Why 10 Comes Before 2

• 6 min read

The live sorter's default Sort is Array.prototype.sort with no compare function: ECMA-262 UTF-16 code units, not localeCompare. A (65) precedes a (97). 10 precedes 2 because 1 is 49 and 2 is 50. Ignore case keys with toLowerCase(), still UTF-16. Not Unix sort, not Intl.Collator, not numeric. Nothing is uploaded.

A dictionary in German or Swedish is not the same order as a JavaScript array. The live line sorter splits on LF (after CRLF/CR normalize), then Sort calls Array.prototype.sort() with no compare function. This page names the gaps that change the first line — ASCII case and digits first, then letters a locale would regroup.

Default sort is UTF-16 code units

A before a, Cherry before apple

MDN and ECMA-262: if the compare function is omitted, elements become strings and are compared as sequences of UTF-16 code units — the same test as < on two strings. Uppercase Latin letters occupy 65–90; lowercase occupy 97–122. Load sample is banana, Apple, apple, banana, Cherry. Default Sort yields Apple, Cherry, apple, banana, banana. An en localeCompare typically groups the two apples first.

Apple (65) · Cherry (67) · apple (97) · banana (98)

10 before 2, file10 before file2

Digits are not numbers here. The first differing unit decides: 1 is 49, 2 is 50, so 10 precedes 2 and 80 precedes 9 (MDN's example). Prefixes keep file1, file10, file2. A numeric collator (new Intl.Collator("en", { numeric: true })) would emit file1, file2, file10. Signs are units too: + is 43 and - is 45, so +1 precedes -1.

10, 2 → 10 2 · file1, file10, file2 → file1 file10 file2

Surrogates and letters a locale would regroup

Each surrogate unit is compared alone

Code points above U+FFFF are two UTF-16 units (U+D800–U+DFFF). MDN's pair 𥙑 (U+25651) sorts before fullwidth Z (\uFF3A, U+FF3A) because unit 55381 is less than 65338 — even though the first code point is larger. An emoji such as 😀 (U+1F600, units D83D DE00) therefore sorts after z. This is not a code-point sort and not a grapheme cluster sort.

𥙑 before \uFF3A · A, z, 😀

ö after z here; after o in German; after z in Swedish

ö is U+00F6 (246), so UTF-16 order is O, o, z, ö. localeCompare with de groups ö with o (o, O, ö, z); sv treats it as a later letter (o, O, z, ö). Å is 197, so it sorts after a and Z here. Those locale results come from ICU / CLDR in the engine; this page never asks for them.

Ignore case is still UTF-16

toLowerCase keys, then the same < test

The Ignore case checkbox (Sort and Unique only) does not call localeCompare. It compares toLowerCase() strings. The sample becomes Apple, apple, banana, banana, Cherry. Equal keys keep their input order because ES2019 made sort stable. Unique keeps the first line for each key in a Set. Reverse is reverse() on the split array and does not sort. Output is join('\\n') with no extra trailing newline. Lines are not trimmed; one trailing empty field from a final newline is dropped.

Ignore case sample → Apple, apple, banana, banana, Cherry

Not Unix sort, not a column sorter

GNU sort uses strcoll under LC_COLLATE. The C locale is byte order. Neither path runs in this browser tab. Sort, Unique, and Reverse are buttons — not as-you-type. Load sample fills the five fruit lines and leaves the output empty. Copy writes the displayed string. This is not a CSV column sorter, not localeCompare, not Intl.Collator, and not a natural/numeric sort.

Sort lines in UTF-16 order

Default Sort is code units. 10 before 2. Nothing is uploaded.

Use Line Sorter →

FAQ

Does Sort use localeCompare or the browser locale?
No. With Ignore case off, Sort calls Array.prototype.sort with no compare function. ECMA-262 converts each line to a string and compares UTF-16 code units. That is the same order as the < operator on strings. It does not call localeCompare, does not construct Intl.Collator, and does not read the browser language. A (code unit 65) therefore sorts before a (97).
Why does 10 sort before 2?
Digit characters are compared one code unit at a time. "1" is 49 and "2" is 50, so "10" is less than "2" and "80" is less than "9". The same prefix rule puts file10 before file2. A natural order (file1, file2, file10) needs a numeric collator such as new Intl.Collator("en", { numeric: true }).compare. This page does not offer that option.
What does Ignore case actually compare?
Ignore case (Sort and Unique only) keys each line with toLowerCase(), then compares those keys with the same UTF-16 < / > test — not localeCompare with sensitivity: "base". The sample banana / Apple / apple / banana / Cherry becomes Apple, apple, banana, banana, Cherry. Equal keys keep their original relative order (ES2019 stable sort). Reverse ignores the checkbox and does not sort.
Is this Unix sort, a CSV column sort, or a natural sort?
No. GNU sort uses strcoll under LC_COLLATE; the C locale is byte order, not JavaScript strings. This page never shells out and never reads LC_COLLATE. Lines are not split into columns. Sort, Unique, and Reverse are buttons (not as-you-type). Load sample fills the five fruit lines and does not run Sort. Nothing is uploaded.

Related