Comparators & Stability

In real code you almost never write a sort; you write the rule. Knowing how comparators and stability work is what turns "sort by date, then by name" into one correct line.

beginner⏱ 8 min read
01

Learn

The idea, the mechanics and the cost.

01The comparator is the order

std::sort(v.begin(), v.end(), comp) runs an O(n log n) algorithm (introsort), and the only thing it knows about your data is your function comp(a, b), which answers: should a come before b?

That means you can sort by any rule:

  • descending: return a > b;
  • by a field: return a.score > b.score;
  • by several keys: compare the first key; only if equal, compare the next

In other languages it looks different but works the same: Python sorted(v, key=lambda p: (-p.score, p.name)), Java Comparator.comparing(...). A key function returning a tuple is often the easiest way to get orders on several keys right.

comp(a, b): if (a.score != b.score)return a.score > b.score; return a.name < b.name;beforeGio80Ana95Eka80Luka70after sort(v, comp)Ana95Eka80Gio80Luka70

02Stability

A sort is stable if elements with equal keys keep their original relative order.

Why care? Say the list is already sorted by name and you now sort by grade. With a stable sort, students with the same grade stay in name order, for free. With an unstable sort, they may come out in any order.

  • std::sort is not stable (based on quicksort).
  • std::stable_sort is stable (based on merge sort), slightly slower and may use O(n) memory.
  • Python's sort and Java's object sort are always stable.

This gives a classic trick: to sort by key A then key B, sort stably by B first, then by A. Or just use one comparator on (A, B).

input (already sorted by name)AnaBEkaBGioALukaAsort by gradestable: Ana stays before EkaGioALukaAAnaBEkaBunstable: equal keys may swapLukaAGioAEkaBAnaB?

03The rule your comparator must follow

The comparator must behave like a strict "less than" (a strict weak ordering):

  • comp(a, a) is false: nothing comes before itself
  • if comp(a, b) is true, comp(b, a) must be false
  • it must be transitive: a before b and b before c means a before c

The classic bug is writing <= instead of <. Then comp(x, x) is true, the algorithm's assumptions break, and std::sort can return garbage or even read past the end of the array and crash. Another trap: comparing doubles that may be NaN, or a "random" comparator to shuffle.

Also keep the comparator cheap: it runs about n log n times. Precompute values like ratios once instead of recomputing them in every call.

✗ bug: true for equal itemsreturn a.x <= b.x;✓ correct: strict "less than"return a.x < b.x;comp(x, x) must always be false, otherwise sort can crash

04Sorting as the first step

Many problems become easy right after a sort with the right comparator:

  • Greedy choices: sort events by end time and keep taking the compatible one that ends earliest (activity selection); sort items by value per kilogram for the fractional knapsack.
  • Intervals: sort by start, then merge overlaps in one pass.
  • Sweep line: turn arrivals and departures into events, sort them, scan while keeping a counter.
  • Binary search and two pointers both need sorted data.

The pattern costs O(n log n) for the sort plus usually O(n) for the scan. When a problem feels like it needs all pairs, ask first: would sorting by some key make the answer appear in order?

Cost at a glance

std::sortO(n log n)
std::stable_sortO(n log n)
Comparator calls≈ n log n
Sort + one scanO(n log n)

Remember

  1. A comparator answers "should a come before b?"; compare keys in priority order to sort by several fields.
  2. Stable sorts keep equal keys in input order; std::sort is not stable, std::stable_sort is.
  3. Use a strict < in comparators: comp(x, x) must be false, or the sort can break.
02

Play

Step through the algorithm, then try it on your own input.

👀 What to watch: Each "compare" step is one call to the comparator. Try your own items (value/weight) and predict the final order before you play.

Interactive visualizerfocus here, then space ← →
✎ Your inputUp to 7 items written value/weight, e.g. 60/10 40/5.

std::sort: ordered by a comparator (value/weight, descending)

$60 / 10kg
6.0 $/kg
$100 / 20kg
5.0 $/kg
$120 / 30kg
4.0 $/kg
$40 / 5kg
8.0 $/kg
$45 / 15kg
3.0 $/kg
std::sort takes ANY comparator that defines a strict weak order. We sort 5 items by value per kg, highest first.

Pseudocode

 1 bool comp(a, b) { return a.ratio > b.ratio; }  // strict weak order 2 compute each ratio = value / weight 3 sort(items, items+n, comp)  // O(n log n) 4 // items now ordered best-value-per-kg first
1 / 1
03

Check

Three questions. Pick an answer to see why.

Q1

Which comparator sorts pairs by first value descending, then by second value ascending?

Q2

Records are sorted by name. You sort them stably by city. How are people from the same city ordered?

Q3

Your std::sort crashes only on inputs with many equal elements. The comparator is return a.v <= b.v;. Why?

04

Practice

Real problems to lock it in, easiest first.