How to Compare Strings in TypeScript (===, localeCompare, Collator)

You compare strings in TypeScript with === when you need an exact, case-sensitive match, and with localeCompare() when you need ordering or a case-insensitive match. localeCompare() returns a negative number, 0 or a positive number:

a === b;                                                    // exact match
a.localeCompare(b, "en-US", { sensitivity: "base" }) === 0;  // ignore case and accents

Every snippet here was compiled in strict mode with TypeScript 7.0.2 and Node.js v22.22.2; the outputs are pasted straight from my console.

I’ll walk through equality, case-insensitive checks, the four sensitivity levels, sorting with Intl.Collator, partial matches and Unicode gotchas.

Compare two strings for equality with ===

Strict equality checks that both values are strings with exactly the same characters. Case matters, and a boxed String object never equals a primitive:

const saved: string = "Denver";
const typed: string = "denver";

console.log("Denver === Denver:", saved === "Denver");
console.log("Denver === denver:", saved === typed);
console.log("Denver !== denver:", saved !== typed);

const boxed = new String("Denver");
console.log("object === primitive:", (boxed as unknown) === saved);
console.log("valueOf() === primitive:", boxed.valueOf() === saved);

Output:

Denver === Denver: true
Denver === denver: false
Denver !== denver: true
object === primitive: false
valueOf() === primitive: true
Command Prompt output of TypeScript comparing strings with === and !==, plus a String object versus a primitive
Matching case returns true, different case returns false, and valueOf() unwraps the boxed string.

Stick to primitive string values and you’ll never hit the boxed case. If you need to rule out blank input first, see how to check if a string is empty in TypeScript.

Why does TypeScript reject comparing a string to a number?

In plain JavaScript, "80202" == 80202 is true because == converts types. Strict TypeScript flags the comparison before it ever runs:

const zip: string = "80202";
const code: number = 80202;

if (zip === code) {
  console.log("same");
}

Output:

cmp_loose.ts(4,5): error TS2367: This comparison appears to be unintentional because the types 'string' and 'number' have no overlap.
Command Prompt output of the TypeScript compiler error: this comparison appears to be unintentional because string and number have no overlap
The compiler stops a string-to-number comparison that would always be false with ===.

Convert one side explicitly, for example Number(zip) === code. I cover the safe conversions in converting a string to a number in TypeScript.

Compare strings ignoring case in TypeScript

Lowercasing both sides works for plain English text. localeCompare() with sensitivity: "base" goes further and ignores accents too:

const a = "Seattle";
const b = "SEATTLE";

console.log("toLowerCase:", a.toLowerCase() === b.toLowerCase());

const same = (x: string, y: string): boolean =>
  x.localeCompare(y, "en-US", { sensitivity: "base" }) === 0;

console.log("base, Seattle vs SEATTLE:", same(a, b));
console.log("base, cafe vs Café:     ", same("cafe", "Café"));
console.log("base, Austin vs Boston: ", same("Austin", "Boston"));

Output:

toLowerCase: true
base, Seattle vs SEATTLE: true
base, cafe vs Café:      true
base, Austin vs Boston:  false

Pick toLowerCase() for IDs and emails, and localeCompare() for names people type. More case tricks are in converting a string to lowercase in TypeScript.

localeCompare() sensitivity options explained

The sensitivity option decides which differences count. Here’s every level against the same pairs:

const pairs: [string, string][] = [["a", "b"], ["a", "a"], ["b", "a"], ["a", "A"], ["a", "á"]];
const levels = ["base", "accent", "case", "variant"] as const;

console.log("pair   " + levels.map((l) => l.padStart(8)).join(""));
for (const [x, y] of pairs) {
  const row = levels.map((s) =>
    String(x.localeCompare(y, "en-US", { sensitivity: s })).padStart(8)
  );
  console.log(`${x} vs ${y} ${row.join("")}`);
}

Output:

pair       base  accent    case variant
a vs b       -1      -1      -1      -1
a vs a        0       0       0       0
b vs a        1       1       1       1
a vs A        0       0      -1      -1
a vs á        0      -1       0      -1
Command Prompt table of TypeScript localeCompare results for base, accent, case and variant sensitivity
base treats a, A and á as equal; accent separates á; case separates A; variant separates both.

Don’t test for exactly -1 or 1. MDN only promises a negative or positive number, so compare against 0. The default for sorting is variant.

Sort strings with localeCompare and Intl.Collator

The default sort() compares UTF-16 code units, so uppercase comes before lowercase and accented letters land at the end. A collator fixes both, and numeric: true puts file2 before file10:

const files = ["file10.txt", "File2.txt", "file1.txt", "éclair.txt", "zebra.txt"];

console.log("sort():          ", [...files].sort().join(", "));
console.log("localeCompare:   ", [...files].sort((a, b) => a.localeCompare(b, "en-US")).join(", "));

const collator = new Intl.Collator("en-US", { numeric: true, sensitivity: "base" });
console.log("Collator numeric:", [...files].sort(collator.compare).join(", "));

Output:

sort():           File2.txt, file1.txt, file10.txt, zebra.txt, éclair.txt
localeCompare:    éclair.txt, file1.txt, file10.txt, File2.txt, zebra.txt
Collator numeric: éclair.txt, file1.txt, File2.txt, file10.txt, zebra.txt
Command Prompt output of TypeScript sorting file names with sort(), localeCompare and Intl.Collator with numeric true
Only the numeric collator gives the order a person expects: file1, File2, file10.

For big lists, create one Intl.Collator and reuse its compare; it’s faster than calling localeCompare() with a locale on every comparison.

For arrays of objects, call the same comparer on a property. See sorting an array alphabetically in TypeScript and sorting objects by property value.

Check part of a string and handle Unicode

Partial checks use includes(), startsWith() and endsWith(), all case-sensitive. Accented text can also be stored two different ways, which breaks === until you call normalize():

const email = "Orders@Example.com";
const composed: string = "caf\u00e9";      // é as one code point
const decomposed: string = "cafe\u0301";   // e + combining accent

console.log("includes @example:", email.toLowerCase().includes("@example"));
console.log("startsWith Orders:", email.startsWith("Orders"));
console.log("endsWith .com:    ", email.endsWith(".com"));
console.log("composed === decomposed:", composed === decomposed);
console.log("after normalize():      ", composed.normalize() === decomposed.normalize());
console.log("lengths:", composed.length, decomposed.length);

Output:

includes @example: true
startsWith Orders: true
endsWith .com:     true
composed === decomposed: false
after normalize():       true
lengths: 4 5

The decomposed string is one character longer, yet both print as café. For more substring patterns, read how to check if a string contains a substring in TypeScript.

Ways to compare strings in TypeScript compared

MethodUse it forCase-sensitive?
=== / !==Exact matches, IDs, tokensYes
toLowerCase() on both sidesSimple case-insensitive checksNo
localeCompare(b, "en-US", opts)Ordering, accent-aware equalityDepends on sensitivity
Intl.CollatorSorting large lists, natural numbersDepends on sensitivity
includes / startsWith / endsWithPartial matchesYes

Once comparisons feel solid, these related TypeScript string tutorials are worth a look:

Frequently asked questions

What is the best way to compare strings in TypeScript?

Use === for exact matches. Use localeCompare() or Intl.Collator when you need ordering or want to ignore case or accents.

Should I use == or === for strings?

Use ===. It doesn’t convert types, and strict TypeScript already blocks most mixed-type comparisons.

How do I compare strings case-insensitively?

Use a.localeCompare(b, "en-US", { sensitivity: "base" }) === 0, or lowercase both sides for plain ASCII text.

What does localeCompare return?

A negative number when the first string sorts before the second, 0 when they’re equivalent, and a positive number when it sorts after.

Why does sort() put “Zebra” before “apple”?

Default sort() compares UTF-16 code units, and uppercase letters have lower codes. Pass localeCompare or a collator’s compare.

Where are the sensitivity options documented?