Sorting stability and safe comparators

Collections · lesson 27 of 42 · 3 min read

See why two sorts in a row still work, and why comparing with a - b is a real bug.

Open this lesson in the learning hub

Key points

  • List.sort uses TimSort, which is stable: elements that compare equal keep their existing order.
  • That is why sorting by name and then by team leaves every team still ordered by name.
  • One comparator with thenComparing is clearer and faster than two separate sorting passes.
  • Never compare with a - b. It overflows for large or negative values and silently reverses the order.
  • Use Integer.compare, comparingInt or Comparator.comparing — all of them are overflow safe.
  • Arrays.sort on primitives is a quicksort and is not stable, but identical primitives are indistinguishable anyway.

Example

import java.util.*;

public class Main {

    record Row(String name, String team) {
        @Override public String toString() { return name + "/" + team; }
    }

    public static void main(String[] args) {
        List<Row> rows = new ArrayList<>(List.of(
                new Row("di", "ops"), new Row("cy", "eng"),
                new Row("bo", "ops"), new Row("ana", "eng")));

        rows.sort(Comparator.comparing(Row::name));
        System.out.println("sorted by name    : " + rows);
        rows.sort(Comparator.comparing(Row::team));
        System.out.println("then by team      : " + rows);
        System.out.println("inside each team the name order survived - that is stability");

        rows.sort(Comparator.comparing(Row::team).thenComparing(Row::name));
        System.out.println("one comparator    : " + rows + "   (clearer than two passes)");

        int big = Integer.MAX_VALUE;
        int small = -10;
        System.out.println("big - small       : " + (big - small) + "   (overflowed, wrong sign)");
        System.out.println("Integer.compare   : " + Integer.compare(big, small) + "   (always right)");

        int[] prims = {3, 1, 2};
        Arrays.sort(prims);
        System.out.println("primitive sort    : " + Arrays.toString(prims) + "   (quicksort, stability meaningless)");
    }
}

Stability is what lets you sort in layers; Integer.compare is what keeps each layer correct.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Collections course, and every lesson in it is listed on the Collections contents page.