skip to content

How does String.compareTo() work, and how does it differ from equals() for ordering and sorting strings?

level: middleimportance: should knowfreq 60%

answer

  1. compareTo returns negative/zero/positive
  2. Lexicographic = char-by-char UTF-16 compare
  3. Zero iff equals() is true (consistent)
  4. Uppercase sorts before lowercase
  5. Use Collator/CASE_INSENSITIVE_ORDER for human order

basics

~20 s

equals() answers a yes/no question: are two strings the same text? compareTo() answers an ordering question: which comes first alphabetically. It returns a negative number, zero, or a positive number, and is what sorting uses to put strings in order.

solid answer

~50 s

equals() returns a boolean for content equality. compareTo() implements the Comparable interface and returns an int: negative if this string sorts before the argument, zero if they are equal, positive if it sorts after. It compares lexicographically — character by character using Unicode (UTF-16) code-unit values; at the first differing position it returns the difference of the two char values, and if one string is a prefix of the other it returns the length difference. This ordering is consistent with equals(): compareTo() returns 0 exactly when equals() is true. It is what Collections.sort, TreeMap/TreeSet, and Arrays.sort use to order strings. A key caveat: compareTo() is case-sensitive (all uppercase letters sort before all lowercase because of their code-unit ranges) and is not locale-aware, so for human-facing alphabetical order you should use a Collator or String.CASE_INSENSITIVE_ORDER / compareToIgnoreCase instead.

code

java · 8 lines
java
System.out.println("apple".compareTo("apple")); // 0
System.out.println("apple".compareTo("apply")); // -20 ('e' - 'y')
System.out.println("app".compareTo("apple"));   // -2  (prefix shorter)
System.out.println("Zebra".compareTo("apple")); // negative: 'Z'(90) < 'a'(97)

List<String> names = new ArrayList<>(List.of("banana", "Apple", "cherry"));
names.sort(String.CASE_INSENSITIVE_ORDER);
System.out.println(names); // [Apple, banana, cherry]

go deeper

for a junior

Knows compareTo gives an order (negative/zero/positive) and that sorting uses it, while equals gives yes/no.

for a middle

Explains lexicographic char-by-char comparison, the prefix length-difference rule, consistency with equals, and case sensitivity.

for a senior

Discusses the Comparable contract, compareTo/equals consistency and its effect on TreeMap/TreeSet, and chooses Collator/CASE_INSENSITIVE_ORDER for correct human ordering.

for a principal

Sets ordering conventions across a codebase (locale-aware collation, comparator consistency with equals, stable sorting), and reasons about i18n correctness and performance of comparison-heavy sorts.

## Two different questions `equals()` and `compareTo()` answer fundamentally different questions about two strings: - `equals(other)` → **boolean**: *are these the same text?* (yes/no) - `compareTo(other)` → **int**: *what is their relative order?* (before / equal / after) You use `equals()` for membership and matching; you use `compareTo()` for **sorting and ordering**. ## The Comparable contract `String` implements `Comparable<String>`, which requires one method: `int compareTo(String other)`. The convention for the returned `int` is: - **negative** → `this` comes *before* `other` - **zero** → `this` is considered *equal in order* to `other` - **positive** → `this` comes *after* `other` Only the sign matters in principle, though `String` returns specific magnitudes (see below). Sorting machinery (`Arrays.sort`, `Collections.sort`, `TreeSet`, `TreeMap`) calls `compareTo` to arrange elements. ## How String.compareTo computes the result (lexicographic order) *Lexicographic* means dictionary-style, comparing character by character from the start: 1. Walk both strings position by position. 2. At the **first position where the characters differ**, return `this.charAt(i) - other.charAt(i)` — the numeric difference of their UTF-16 code-unit (`char`) values. 3. If no differing position is found within the shorter length, the strings share a common prefix; return `this.length() - other.length()` (the shorter one sorts first). Examples: ```java "apple".compareTo("apple"); // 0 (identical) "apple".compareTo("apply"); // < 0 ('e'(101) - 'y'(121) = -20) "apple".compareTo("app"); // 2 ("app" is a prefix; 5 - 3 = 2) "app".compareTo("apple"); // -2 ``` ## Consistency with equals() For `String`, `compareTo` is **consistent with `equals`**: `a.compareTo(b) == 0` *if and only if* `a.equals(b)` is `true`. This consistency is important — `TreeMap`/`TreeSet` use `compareTo` (not `equals`) to decide key uniqueness, so a comparator inconsistent with equals can cause a sorted set to 'lose' elements it considers equal. `String`'s natural ordering avoids that pitfall. ## The case-sensitivity and locale traps `compareTo` compares raw `char` code-unit values. In Unicode/ASCII, **all uppercase letters (A–Z, 65–90) come before all lowercase letters (a–z, 97–122)**. So: ```java "Zebra".compareTo("apple"); // negative — 'Z'(90) < 'a'(97), so "Zebra" sorts FIRST ``` That is rarely what a human expects from alphabetical order. Two fixes: - Case-insensitive: `compareToIgnoreCase(...)` or sort with `String.CASE_INSENSITIVE_ORDER`. - Locale-correct human ordering (accents, language rules): use `java.text.Collator` configured for a `Locale`. `compareTo` is *not* locale-aware and will mis-order accented or non-English text. ## Putting it together ```java List<String> names = new ArrayList<>(List.of("banana", "Apple", "cherry")); Collections.sort(names); // [Apple, banana, cherry] — uppercase first names.sort(String.CASE_INSENSITIVE_ORDER); // [Apple, banana, cherry] — case-insensitive names.sort(Collator.getInstance(Locale.ENGLISH)); // locale-correct alphabetical ``` ## Summary rule Use `equals()` to ask 'same?'; use `compareTo()` (or a comparator) to ask 'in what order?'. For user-visible alphabetical order, prefer case-insensitive ordering or a `Collator`, because raw `compareTo` is code-unit-based and case-sensitive.

  • Why might a TreeSet 'drop' a string you expected it to keep separate?
    TreeSet uses compareTo (or its comparator), not equals, to decide uniqueness. If your comparator returns 0 for two distinct strings (e.g. a case-insensitive comparator treating "a" and "A" as equal), the set keeps only one. The ordering must be consistent with the equality you want.
  • What does "app".compareTo("apple") return and why?
    A negative number (-2). "app" is a proper prefix of "apple", so after matching characters compareTo returns the length difference 3 - 5 = -2, meaning "app" sorts first.

equals() is a turnstile (you match or you don't). compareTo() is a queue position: it tells you whether you stand ahead of, beside, or behind the other person in line.

saying these in an interview costs you the question

  • Saying compareTo returns a boolean
  • Assuming compareTo is case-insensitive or locale-aware
  • Thinking lowercase sorts before uppercase
  • Using compareTo for equality instead of equals() (works but obscures intent)
  • Relying on the exact magnitude rather than the sign for general comparators

context