For what inputs is std::sort unstable?

A sorting algorithm is “stable” if, for two equivalent elements, it preserves their original order relative to each other. It might be useful to know a concrete example of input for which your library’s std::sort is unstable. Here are examples for libc++ and libstdc++ (as of September 2020). Godbolt.

auto LessMod2 = [](int a, int b) {
    return (a % 2) < (b % 2);
};

// For libc++:
std::vector<int> v1 = {
    0, 1, 0, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3,
};
std::sort(v1.begin(), v1.end(), LessMod2);
// 0 2 0 1 1 ... 1 1 3

The above 31-element input sorts unstably for both libc++ and libstdc++. However, libstdc++ also sorts the following 17-element input unstably:

// For libstdc++:
std::vector<int> v2 = {
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 3,
};
std::sort(v2.begin(), v2.end(), LessMod2);
// 1 3 1 1 ... 1 1 1

As of September 2020, it appears that libc++ std::sort happens to be stable for all ranges of size less than 31, and libstdc++ std::sort happens to be stable for all ranges of size less than 17. (Do not rely on this little factoid in production!)

To be clear: There’s nothing wrong with this. As the caller, if you want stable sorting, you should use std::stable_sort, not std::sort.

As the vendor, if you don’t intend to guarantee stability, it might be a good idea to break stability for small inputs, just to teach your users not to depend on that property by accident or coincidence.


Notice that by definition, std::stable_sort never permutes a range that is already sorted.

auto orig = x;
if (std::is_sorted(x.begin(), x.end(), comp)) {
    std::stable_sort(x.begin(), x.end(), comp);
}
assert(x == orig);

I find it interesting that libstdc++’s std::sort will permute a sorted range; notice that the example input v2 is trivially sorted, and yet libstdc++’s std::sort swaps some of the elements. I’m not sure whether libc++’s std::sort will ever permute a sorted range.

Posted 2020-09-11