completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Comparing & sorting text

Compare strings by code units, sort human text with localeCompare and Intl.Collator, handle locale-aware case conversion, and build case-insensitive search that respects accents.

By the end, you can
  • 01
    Explain the default orderSay why the default order puts capital letters and accented letters in surprising places.
  • 02
    Pick a language-aware toolUse localeCompare for one comparison, and one Intl.Collator for a big sort.
  • 03
    Build a friendly searchChoose on purpose how to handle capitals and accents, instead of hoping toLowerCase() works for every language.

People and JavaScript order text differently

A string is a piece of text, like "apple". JavaScript can compare any two strings. But by default it does not compare them like a dictionary does. It compares the numbers it stores for each piece of the text. These numbers are called UTF-16 code units. You met them in the Unicode & string internals lesson.

Code units are good for exact rules that machines follow. But they often give a strange order for names, titles, product lists, and search boxes. This lesson shows the tools that order text the way people expect.

This lesson builds on three earlier lessons: Comparisons, Sorting & copying safely, and Unicode & string internals. The Internationalization with Intl lesson comes later and goes further. You will still write the same kind of sort function. Only the part that compares two strings changes.

One-line definition

Text collation is a set of rules for ordering text. For two strings, it says whether the first comes before the second, after it, or counts as equal. The rules depend on the language and on the job, such as sorting or searching.

Real-life analogySorting names in a contact list

Every letter has a hidden number. A machine can sort names by those numbers. Your phone sorts your contacts in the order people expect for your language. JavaScript can do both.

In real life: The hidden number behind each letter
In JavaScript: A letter’s UTF-16 code unit
In real life: Sorting names by those numbers
In JavaScript: Default <, >, and sort() on strings
In real life: Your phone’s contact order for your language
In JavaScript: localeCompare with a locale such as "de"
In real life: One saved setting for the whole contact list
In JavaScript: One Intl.Collator, made once and used again

By default, JavaScript compares code units

STEP THROUGH

When both sides of < are strings, JavaScript reads them one code unit at a time, from the left. It stops at the first place where they differ. The string with the smaller number there comes first. Array sort() uses the same rule for strings when you don't give it a compare function.

The numbers behind the first lettersPop out in the code editor (opens in a new tab)JavaScript
console.log("a".charCodeAt(0)); // 97console.log("Z".charCodeAt(0)); // 90 console.log("Zebra" < "apple"); // true

charCodeAt(0) gives the code unit at position 0, which is the first letter. Line 1 prints 97 for small a. Line 2 prints 90 for capital Z. Every plain capital letter has a smaller number than every plain small letter. So line 4 prints true: Zebra comes before apple.

Comparing "Zebra" with "apple". The first letters are Z, code unit 90, and a, code unit 97. 90 is smaller than 97, so Zebra comes before apple."Zebra" < "apple"Zcode unit 9090 < 97acode unit 97So Zebra comes before apple
JavaScript compares the numbers behind the first letters. Capital Z is 90 and small a is 97. 90 is smaller, so Zebra comes first, even though a dictionary puts apple first.

Code-unit order is not a mistake. It is an exact rule for machines, not the rule of any language. It also explains why "10" < "9" is true. The first code units already differ: "1" comes before "9", so JavaScript never looks further.

A compare function says which comes firstPop out in the code editor (opens in a new tab)JavaScript
const collator = new Intl.Collator("en");const result = collator.compare("apple", "banana"); console.log(result < 0); // true: apple comes before bananaconsole.log(Math.sign(result)); // -1, but only the sign is guaranteed

A comparator is a compare function: it takes two values, a and b, and tells sort() which one comes first. It returns a number:

  • A negative number means a comes first.
  • Zero means they count as equal for this rule.
  • A positive number means b comes first.

Line 1 makes an Intl.Collator, a tool that compares text with English rules. You'll learn it properly below. Line 2 compares apple with banana and gets a negative number, so line 4 prints true. Line 5 uses Math.sign, which turns any number into -1, 0, or 1. Only the sign of the result is promised. So don't write code that expects exactly -1 or 1.

Sort by code units or by English rules?
Step 0 of 3Ready
Your turn: follow the blue line

Pick a way to compare, guess the printed order, then step through the run.

Running in
  1. script
Next: line 1
const sorted = [...words].sort();console.log(sorted.join(", "));
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Change line 2: how should sort() compare the words?

A new choice starts the replay again. Guess the printed order before you step through.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Sort a copy, so the original order staysPop out in the code editor (opens in a new tab)JavaScript
const names = ["Zoe", "Asha", "Mina"];const sortedNames = [...names].sort((a, b) => a.localeCompare(b, "en")); console.log(names.join(", "));console.log(sortedNames.join(", "));

[...names] uses the spread syntax. It makes a new array with the same items, called a shallow copy. sort() changes the array it is called on, so here it changes only the copy. Line 2 compares with localeCompare, which the next section explains. Line 4 prints "Zoe, Asha, Mina", the original order. Line 5 prints "Asha, Mina, Zoe", the sorted copy. Do this when another part of the page still needs the original order.

Use the default order for IDs, exact codes that programs send to each other, and other places where you want the exact machine rule. For names and labels that people read, use a comparison that knows the language.

localeCompare compares text like a dictionary

INTERACTIVE

Every string has a localeCompare method. You call it on one string and pass the other one: a.localeCompare(b, locale). A locale is a short code for a language, such as "en" for English or "de" for German.

localeCompare returns the same kind of number as a comparator. Negative means "comes before". Zero means "counts as equal here". Positive means "comes after". So a sort function can return it directly.

Compare one pair with German and Swedish rulesPop out in the code editor (opens in a new tab)JavaScript
"äpfel".localeCompare("zebra", "de"); // negative"äpfel".localeCompare("zebra", "sv"); // positive[...names].sort((a, b) => a.localeCompare(b, "de"));
Check the sign for each languagePop out in the code editor (opens in a new tab)JavaScript
const german = "äpfel".localeCompare("zebra", "de");const swedish = "äpfel".localeCompare("zebra", "sv"); console.log(german < 0); // true: German places ä near aconsole.log(swedish > 0); // true: Swedish places ä after z

Always pass the locale: for example "en" (English), "de" (German), "sv" (Swedish), or "tr" (Turkish). If you leave it out, JavaScript uses the default language of the browser or computer. That can be right for an app that follows the user's language settings. In examples and tests, write the locale, so the result is the same on every computer.

In both blocks, line 1 uses German rules and line 2 uses Swedish rules on the same two words. German sorts ä next to a. Swedish treats ä as its own letter near the end of the alphabet, after z. The words did not change. Only the rules did. In the first block, line 3 uses localeCompare as the compare function for a whole sort.

The letters a, ä, and z sorted with two sets of rules. German order: a, ä, z, so äpfel comes before zebra. Swedish order: a, z, ä, so zebra comes before äpfel.German"de"aäzä next to aäpfel comes firstSwedish"sv"azää after zzebra comes first
The same three letters, sorted with two languages' rules. German puts ä next to a. Swedish puts ä after z. So äpfel comes first in German and last in Swedish.

localeCompare can also take a third argument with options. One option, sensitivity, says which differences count, such as capital letters or accents. The playground shows two of its settings at the bottom.

Sort the same words with three languages' rules
Sort with the chosen localePop out in the code editor (opens in a new tab)JavaScript
const words = ["zebra", "äpfel", "apfel", "åre", "öl"];const collator = new Intl.Collator(locale);const sorted = [...words].sort(collator.compare);
Sorted with en5 words
apfeläpfelåreölzebra

Compare a with á. With sensitivity: "base" the result is 0, which means equal. With sensitivity: "accent" it is -1, which means different.

Try it yourself
Locale:

English and German sort these accented words next to their plain letters. Pick sv to see the Swedish order.

Language rules can differ a little between browsers. This list is sorted in your own browser, with the locale you pick.
Real-life analogyHow closely should your phone match names?

Picture a phone setting for how closely to match contact names. When you search, a, A, and á could all count as the same letter. When you check spelling, the accent should count.

In real life: Ignore capitals and accents
In JavaScript: sensitivity: "base"
In real life: Notice accents, ignore capitals
In JavaScript: sensitivity: "accent"
In real life: Notice capitals, ignore accents
In JavaScript: sensitivity: "case"
In real life: Notice capitals and accents
In JavaScript: sensitivity: "variant"

Intl.Collator: set the rules once, compare many times

SORTER

localeCompare is handy for one or two comparisons. But a sort compares many pairs. For a sort, make one Intl.Collator. A collator is an object that holds one locale and one set of options.

A collator has a compare method. It is a ready-made comparator, so you can pass collator.compare straight to sort(). The rules are set up once, not again for every pair.

The table lists the options you'll use most. Read each row as: this option, what it changes, and a short example.

Options you can pass to Intl.Collator
OptionWhat does it change?Example
sensitivity: "base"Capital letters and accents don't count.In English, a, A, and á count as equal.
sensitivity: "accent"Capital letters don't count, but accents do.a and A are equal; a and á are different.
sensitivity: "case"Accents don't count, but capital letters do.a and á are equal; a and A are different.
sensitivity: "variant"Capital letters, accents, and other small differences all count.a, A, and á are all different.
numeric: trueA run of digits is compared as one whole number.item9 sorts before item10.
caseFirstSays whether capital or small letters sort first."upper", "lower", or "false" (the language's own rule).
Code-unit order or locale order?
  • "Zebra" < "apple"
  • ["banana", "apple", "Cherry"].sort()
  • names.sort((a, b) => a.localeCompare(b, "de"))
  • names.sort(new Intl.Collator("sv").compare)
  • new Intl.Collator("en", { numeric: true })
  • "a".charCodeAt(0) > "Z".charCodeAt(0)
Try it yourself
0 of 6 correct

Put each line of code under the rule it uses.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

numeric: true helps most with file names. Without it, item10 sorts before item9. The first character that differs is 1 in one name and 9 in the other, and 1 comes first. With numeric: true, a run of digits is compared as one whole number, so 9 comes before 10.

Left, without the numeric option: item1, item10, item9, so 10 comes before 9. Right, with numeric: true: item1, item9, item10, so 9 comes before 10.Without numericitem1item10item910 before 9With numeric: trueitem1item9item109 before 10
On the left, digits are compared one at a time. 1 comes before 9, so item10 lands before item9. On the right, numeric: true reads each run of digits as one whole number.

Capital letters can depend on the language

INTERACTIVE

toUpperCase() turns text into capital letters. toLowerCase() turns it into small letters. Changing between capital and small letters is called case conversion. These two methods use rules that suit most languages. A few languages need their own rules. For those, use toLocaleUpperCase(locale) and toLocaleLowerCase(locale), and name the language.

Capital letters follow the language
Capital and small letters in Turkish and GermanPop out in the code editor (opens in a new tab)JavaScript
"i".toLocaleUpperCase("tr"); // "İ""I".toLocaleLowerCase("tr"); // "ı""ß".toLocaleUpperCase("de"); // "SS"
Results from your browserlocale named in the code
Turkish capital of iİ
Turkish small letter of Iı
German capitals of ßSS
Try it yourself
These examples don't change

Turkish has an i with a dot and an i without one. German ß becomes SS in capitals. The locale methods let you say which language's rules to use.

Each line names its locale, tr or de, so the result doesn't depend on your browser's language.

Turkish has two different i letters: i with a dot and ı without one. Each has its own capital. So with Turkish rules, the capital of i is İ, a capital I with a dot. German is different again: JavaScript turns ß into two capitals, SS.

Capital letters can make text longerPop out in the code editor (opens in a new tab)JavaScript
const upperName = "Straße".toLocaleUpperCase("de"); console.log(upperName); // "STRASSE"console.log(upperName.length); // 7, not 6

Line 1 makes the German capitals of Straße. Line 3 prints "STRASSE". Line 4 prints 7, one more than the 6 letters we started with. So one letter can become two.

This matters when you save or compare text after changing it. Don't assume each letter stays one letter. And don't write your own table of capital letters. Use these methods with the right locale.

Where you’ll use this

You'll compare text anywhere people look through a list: contact names, countries, product titles, table columns, menus of commands, and file names. A safe pattern has three parts:

  • Keep the original string to show on the screen.
  • Make a compare or search helper for the job.
  • Write the locale and the options in your code.
Search and sort a product listPop out in the code editor (opens in a new tab)JavaScript
const collator = new Intl.Collator(userLocale, {  sensitivity: "base",  numeric: true,}); const visibleProducts = products  .filter((product) => matchesSearch(product.name, query))  .sort((a, b) => collator.compare(a.name, b.name));

Lines 1 to 4 make one collator from the user's locale, userLocale. Line 2 says to ignore capitals and accents. Line 3 says to compare digits as whole numbers. Line 7 keeps only the products that match the search. Line 8 sorts them by name with the collator.

Make the collator once

For a big sort, make one Intl.Collator and use it for every pair. Then the locale and options are set up once, not for every pair. This is a good habit. It doesn't promise the same speed-up in every browser.

Choose the locale on purpose

Take the locale from the user's profile or the app's settings when you can. That is usually better than the computer's default. Use code-unit comparison for IDs and exact codes. Use locale rules for text people read. Never use a friendly comparison to check permissions or whether a name is already taken: two different strings can count as equal.

Common misconceptions

  • "Alphabetical order is the same everywhere." Languages put ä, å, and ö in different places. German puts ä next to a. Swedish puts it after z.
  • "sort() knows my users' language." With no compare function, sort() uses code-unit order.
  • "localeCompare always returns -1 or 1." Only the sign is promised: negative, zero, or positive.
  • "Making both sides small letters fixes search for everyone." é and e still count as different, and language rules like the Turkish i are ignored.
  • "Equal for a collator means the strings are the same." With sensitivity: "base", Résumé and resume count as equal, but === says false.

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upPredict the default sort

Work out the answer in your head before you check. Type the exact order that the default sort prints.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const words = ["banana", "apple", "Cherry", "éclair"];
console.log(words.sort().join(", "));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeSort names for German users

    Sort the names with German rules. Sort a copy, so the original array stays the same.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const names = ["Özil", "Oden", "Änne", "Anna"];
    const collator = new Intl.Collator("de");
    console.log([...names].sort(collator.compare).join(", "));

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeSort file names by their numbers

      Make the file names sort the way a file picker does: 1, 2, 10.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const files = ["file10", "file2", "file1"];
      const collator = new Intl.Collator("en", { numeric: true });
      console.log([...files].sort(collator.compare).join(", "));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · ChallengeBuild a search that ignores accents

        Read the fold-and-filter code, then predict which name matches zoe.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        function fold(value) {
          return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
        }
        const names = ["Jose", "José", "Zoë"];
        console.log(names.filter((name) => fold(name).includes(fold("zoe"))).join(", "));

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeFix the equality check that ignores capitals

          This code makes both strings small letters, but it still treats accents as different. Replace it with a collator check that ignores capitals and accents.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          // Bug: accents are treated as different.
          console.log("Résumé".toLowerCase() === "resume".toLowerCase());

          Answer, then press Check. Spacing and letter case don’t matter.

            Check your understanding

            7 QUESTIONS
            Comparing text quiz · 7 questionsScore: first tries count
            1. Question 1 of 7When both values are strings, what does < compare?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does this print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log("Zebra" < "apple");

              Choose an answer to see the explanation.

            3. Question 3 of 7You sort thousands of names on a page, all with the same rules. Which tool fits best?

              Choose an answer to see the explanation.

            4. Question 4 of 7What does this sort print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const c = new Intl.Collator("en", { numeric: true });
              console.log(["item10", "item9", "item1"].sort(c.compare).join(", "));

              Choose an answer to see the explanation.

            5. Question 5 of 7What does this print with Turkish rules?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log("i".toLocaleUpperCase("tr"));

              Choose an answer to see the explanation.

            6. Question 6 of 7You want to check if two strings are equal, ignoring capitals and accents. Which check is clearest?

              Choose an answer to see the explanation.

            7. Question 7 of 7What does this search fold print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const folded = "Résumé".normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase();
              console.log(folded);

              Choose an answer to see the explanation.

            Key takeaways

            • By default, JavaScript compares strings by their UTF-16 code units. The rule is exact and fast, but often not the order people expect.
            • Use localeCompare to compare one pair with a language's rules.
            • Make one Intl.Collator when you sort many strings with the same rules.
            • Capital letters can depend on the language: Turkish has two i letters, and German ß becomes SS.
            • A search that ignores capitals and accents needs clear choices: normalize the text, remove the marks, and pick a locale.

            To compare text well, use the rule your users expect, not just the rule JavaScript uses by default.

            Up next: Destructuring arrays.

            CompleteFrontend Clear concepts. Working examples.