Comparing Standard Sorting Algorithms in Python, JavaScript, Rust, Go, and R
TL;DR
Here is a summary of the standard sorting algorithms in 5 major languages:
| Language | Stable sort | Unstable sort | Base algorithm | Introduced in |
|---|---|---|---|---|
| Python | Timsort (Powersort merge policy) | - | Merge Sort + Insertion Sort | 3.11 (2022) |
| JavaScript (V8) | Timsort | - | Merge Sort + Insertion Sort | Chrome 70 (2018) |
| Rust | driftsort | ipnsort | Merge Sort-based / Quicksort-based | 1.81 (2024) |
| Go | - | pdqsort | Quicksort + Heapsort + Insertion Sort | 1.19 (2022) |
| R | Radix Sort / Shell Sort | - | Auto-selected based on type | R 3.x |
A common trend in recent years is the rise of adaptive sorting that leverages existing order (pre-sorted runs) and hybrid strategies that combine multiple algorithms.
Background
Sorting is one of the most fundamental algorithms in computer science. Yet surprisingly few people know exactly what their language's sort() does internally.
In fact, between 2022 and 2024, several major languages underwent significant updates to their standard sorting algorithms:
- Python 3.11 (2022): Timsort's merge policy was replaced with Powersort
- Go 1.19 (2022): Switched from introsort-based to pdqsort
- Rust 1.81 (2024): Stable sort replaced with driftsort, unstable sort with ipnsort
This article compares the sorting algorithms adopted in the standard libraries of 5 languages: Python, JavaScript, Rust, Go, and R, examining their mechanisms and design philosophies.
Python -- Timsort + Powersort Merge Policy
Timsort Basics
Python's list.sort() and sorted() use Timsort. Timsort was designed by Tim Peters in 2002 specifically for Python. It is a hybrid stable sort combining Merge Sort and Insertion Sort.
The basic operation of Timsort is as follows:
- Scan the array to find already-sorted subsequences (runs). If a run is in descending order, reverse it to ascending
- If a run is shorter than the minimum run length (typically 32-64), extend it using Insertion Sort
- Push detected runs onto a stack and merge them based on certain conditions
Timsort's strength lies in its excellent performance on "partially sorted sequences," which are common in real-world data. It runs in on already-sorted input and in the worst case.
Powersort Merge Policy in Python 3.11
In Python 3.11 (2022), Timsort's merge policy was replaced with Powersort by J. Ian Munro and Sebastian Wild.
Timsort's original merge policy had two known issues:
- A potential stack overflow discovered through formal verification, affecting both CPython and Java
- The merge order of runs was determined heuristically, leading to unnecessary overhead in some cases
Powersort assigns an integer value called "power" to each pair of adjacent runs, and when a new run is added, merges with higher power are executed first. This approach achieves provably near-optimal approximation with respect to the entropy of the run-length distribution.
Performance improvements of up to 30% have been reported for specific input patterns. However, for general use cases, the difference from Timsort is barely noticeable.
JavaScript -- Engine-Dependent Sort Implementations
JavaScript's sorting algorithm is not specified in the ECMAScript specification, and each engine adopts a different implementation. Since ES2019, stable sorting has been required by the specification.
V8 (Chrome / Node.js) -- Timsort
The V8 engine switched from QuickSort to Timsort in Chrome 70 (2018).
Key characteristics of V8's Timsort implementation:
- Uses Insertion Sort for arrays of 22 elements or fewer
- Applies Timsort for larger arrays
- The implementation is written in V8's proprietary Torque language
SpiderMonkey (Firefox) -- Merge Sort
Mozilla's SpiderMonkey engine uses Merge Sort. It previously used QuickSort but switched to Merge Sort for stability. Adoption of TimSort was considered but rejected due to license incompatibility (GPL v2 vs. MPL 2).
JavaScriptCore (Safari) -- Timsort Variant
Apple's JavaScriptCore engine also uses a variant of Timsort.
Comparison Across Engines
| Engine | Browser | Algorithm |
|---|---|---|
| V8 | Chrome, Edge, Node.js | Timsort |
| SpiderMonkey | Firefox | Merge Sort |
| JavaScriptCore | Safari | Timsort variant |
Rust -- driftsort (stable) / ipnsort (unstable)
Rust overhauled both its stable and unstable sorts in Rust 1.81 (August 2024). This was the largest update to sorting algorithms in Rust's standard library.
Previous Implementation
Before Rust 1.81, the following algorithms were used:
slice::sort(): Modified Timsort (stable sort)slice::sort_unstable(): pdqsort (unstable sort)
driftsort -- The New Stable Sort
driftsort is a stable sort designed by Orson Peters and Lukas Bergdoll, derived from glidesort.
Key features of driftsort:
- Switches between two small-sort implementations at compile time based on type characteristics
- Ancestor pivot tracking enables detection and handling of common elements, achieving comparisons when there are only distinct element values
- Leverages ILP (Instruction-Level Parallelism) instead of SIMD for architecture-independent performance optimization
In terms of performance, over 2x speedup on random input compared to the old implementation and up to 17x speedup on low-cardinality patterns (such as random_d20) have been reported.
ipnsort -- The New Unstable Sort
ipnsort is an unstable sort designed starting from pdqsort.
no_stdcompatible (noalloccrate required)- Guarantees comparisons in the worst case
- for already-sorted ascending or descending input
It achieves 2.4x speedup on random input compared to the old pdqsort.
Design Considerations
The new sort implementations in Rust 1.81 may panic if the comparison function does not satisfy total ordering. The old implementation would silently return incorrect results, but the new implementation is designed to detect inconsistencies.
Go -- pdqsort
In Go 1.19 (August 2022), the sort package's internal algorithm was switched to pdqsort (Pattern-Defeating Quicksort). This proposal came from ByteDance's programming language team.
How pdqsort Works
pdqsort is an algorithm designed by Orson Peters that extends and improves David Musser's introsort. It dynamically switches between QuickSort, HeapSort, and Insertion Sort depending on the situation.
The basic operation is as follows:
- Use Quicksort as the base, selecting a pivot and performing partition
- If no swaps occurred after partitioning, try Insertion Sort (detecting already-sorted sequences)
- If the smaller partition is less than 1/8 of the total, consider it a skewed partition
- If skewed partitions persist, fall back to HeapSort to guarantee
This achieves the following complexities:
- Sorted, reverse-sorted, or all-identical input:
- Average:
- Worst case: (guaranteed by HeapSort fallback)
Go-Specific Modifications
The Go version disables BlockQuicksort optimizations because BlockQuicksort doesn't perform well in Go's runtime environment.
Regarding Stable Sort
Go's sort.Sort() and slices.Sort() are unstable sorts. If a stable sort is needed, use sort.Stable() or slices.SortStableFunc().
Future Developments
A proposal to replace pdqsort with Dual-Pivot Quicksort in Go's standard library has also been made.
R -- Radix Sort / Shell Sort
R's sort() is notable for allowing explicit selection of multiple sorting algorithms via the method argument.
method = "auto" (Default)
The default "auto" automatically selects the algorithm based on the data type:
- Radix Sort is chosen for numeric, integer, logical, and factor types
- Shell Sort is chosen for character types and others
Radix Sort
R's Radix Sort implementation originates from Matt Dowle and Arun Srinivasan's data.table package.
- Complexity is (hash-based, not comparison-based)
- Stable sort
- Falls back to Insertion Sort for small inputs under 200 elements
- Orders of magnitude faster than Shell Sort for character vectors
- Limitation: does not support elements exceeding (long vectors) or complex types
Shell Sort
Shell Sort is the most versatile option, supporting all data types. The implementation is based on Sedgewick's (1986) gap sequence.
- Worst-case complexity is (depends on the gap sequence used)
- Unstable sort
Quick Sort
Selectable via method = "quick", but only supports numeric types. Faster than Shell Sort (about 1.5x for 1 million elements, about 2x for 1 billion elements), but has poor worst-case performance.
Comparison Summary
Algorithm Characteristics Comparison
| Language | Algorithm | Stability | Best | Average | Worst | Introduced |
|---|---|---|---|---|---|---|
| Python | Timsort + Powersort | Stable | 3.11 (2022) | |||
| JS (V8) | Timsort | Stable | Chrome 70 (2018) | |||
| Rust (stable) | driftsort | Stable | 1.81 (2024) | |||
| Rust (unstable) | ipnsort | Unstable | 1.81 (2024) | |||
| Go | pdqsort | Unstable | 1.19 (2022) | |||
| R (numeric) | Radix Sort | Stable | R 3.x | |||
| R (general) | Shell Sort | Unstable | R 1.x |
Common Trends
Two common trends have emerged in sorting algorithm updates since 2022.
The first is adaptive sorting. All languages have adopted algorithms that leverage existing order in the input data. Many operate in on already-sorted input, and since real-world data is often partially sorted, this property has significant practical value.
The second is hybrid strategies. Rather than a single sorting algorithm, the standard approach is to dynamically switch between multiple algorithms based on element count and data characteristics. A typical combination uses Insertion Sort for small arrays, Merge Sort or Quicksort variants for large arrays, and HeapSort as a worst-case fallback.
Conclusion
Although sorting algorithm research is often considered a mature field, the implementations in major languages were successively overhauled in the short period from 2022 to 2024. In particular, Rust's driftsort/ipnsort achieved dramatic performance improvements of up to 17x, demonstrating that there is still room for improvement.
While we rarely think about it in daily work, understanding the characteristics of your language's sort implementation can be helpful for performance tuning and algorithm selection decisions.