Sorting algorithms, animated and compared
Sorting is the most studied problem in computer science, and a good place to learn how algorithms are compared. Run seven classic sorts on the same numbers, step by step, and watch how many comparisons, swaps and writes each one needs.
On this page
Every sorting algorithm below takes a list of numbers and puts it in increasing order. They differ in how: which pairs they compare, how they move values, how much extra memory they need, and how their running time grows. The visualiser highlights the line of pseudocode being executed and explains each step in words. Change the input to see how the algorithms react to sorted, reversed and repetitive data.
Sorting visualiserPick an algorithm, edit the values, then press play or step through.
- Comparing
- Swapping
- Writing
- Pivot / heap root
- Key / current minimum
- In final place
Pseudocode
Counters
This algorithm
Compare all seven algorithms on this input
| Algorithm | Comparisons | Swaps | Writes | Worst case | Stable? |
|---|
A swap exchanges two entries; a write stores one value (merge, insertion and counting sort move values by writes). Counting sort never compares two elements, which is how it beats the n log n lower bound for comparison sorts.
The seven algorithms
| Algorithm | Best | Average | Worst | Extra space | Stable |
|---|---|---|---|---|---|
| Bubble sort | Θ(n) | Θ(n²) | Θ(n²) | Θ(1) | Yes |
| Insertion sort | Θ(n) | Θ(n²) | Θ(n²) | Θ(1) | Yes |
| Selection sort | Θ(n²) | Θ(n²) | Θ(n²) | Θ(1) | No |
| Merge sort | Θ(n log n) | Θ(n log n) | Θ(n log n) | Θ(n) | Yes |
| Quicksort | Θ(n log n) | Θ(n log n) | Θ(n²) | Θ(log n) stack on average | No |
| Heapsort | Θ(n log n) | Θ(n log n) | Θ(n log n) | Θ(1) | No |
| Counting sort | Θ(n + m) | Θ(n + m) | Θ(n + m) | Θ(m) for the counts | Yes |
Bubble sort
Bubble sort walks along the array swapping any neighbouring pair that is out of order. After the first pass the largest value has “bubbled” to the end; after the second, the two largest are in place; and so on. If a whole pass makes no swaps, the array is sorted and it can stop early, which is why its best case is linear. It is easy to understand and almost never the right choice: it makes swaps on reversed input, one for every out-of-order pair.
Insertion sort
Insertion sort grows a sorted prefix. It lifts out the next value (the key), slides every larger value in the prefix one place to the right, and drops the key into the gap. On nearly sorted input each key moves only a little, so insertion sort is fast there, and it is the standard choice for short arrays inside bigger algorithms.
Worked example: insertion sort on [7, 3, 5, 1]
| Step | Array after | What happened |
|---|---|---|
| start | 7 3 5 1 | — |
| i = 1, key 3 | 3 7 5 1 | 7 > 3, shift 7 right; drop 3 at index 0 |
| i = 2, key 5 | 3 5 7 1 | 7 > 5, shift; 3 ≤ 5, stop; drop 5 at index 1 |
| i = 3, key 1 | 1 3 5 7 | 7, 5 and 3 are all > 1, shift each; drop 1 at index 0 |
That took 6 comparisons. Type 7, 3, 5, 1 into the visualiser and choose insertion sort to step through the same run.
Selection sort
Selection sort scans the unsorted part for its minimum and swaps it to the front. It always makes exactly comparisons, whatever the input, but never more than swaps. That makes it a reasonable choice only when writing to memory is much more expensive than reading.
Merge sort
Merge sort is divide and conquer: split the array in half, sort each half recursively, and merge the two sorted halves by repeatedly taking the smaller front element. Merging elements costs at most comparisons, and the halving gives levels, so the total is on every input. The price is a buffer of size . Merge sort is stable, and its sequential access pattern suits linked lists and data on disk.
Quicksort
Quicksort, published by Tony Hoare in 1962, also divides and conquers, but does the work before recursing. It picks a pivot and partitions the array so that smaller values come before it and the rest after; the pivot is then in its final place, and the two sides are sorted independently. The visualiser uses the simple Lomuto partition with the last element as pivot.
When pivots split the array roughly in half, quicksort takes time and, in practice, is usually the fastest of the comparison sorts because its inner loop is tight and cache-friendly. When pivots are consistently the smallest or largest value, each partition removes only one element and it degrades to . With the last element as pivot that happens on input that is already sorted: try the “Reversed” button, then sort the sorted result again. Real implementations avoid this by choosing the pivot randomly or as the median of three samples.
Heapsort
Heapsort first rearranges the array into a max-heap, a binary tree stored in the array where is at least as large as its children and . The maximum is then at the root. It swaps the root to the end, shrinks the heap by one, and sifts the new root down to restore the heap property. It guarantees time with constant extra space, but jumps around memory, so it is usually slower than quicksort in practice.
Counting sort
Counting sort never compares two elements. It counts how many times each value occurs, then writes the values back in order. For integers between 0 and it takes time, so it beats every comparison sort when the range is small. It is the building block of radix sort, which sorts numbers digit by digit.
Stability
A sort is stable if elements that compare equal keep their original order. That matters when you sort records by one field after another. Sort a list of people by first name, then stably by surname, and people with the same surname end up in first-name order. Merge, insertion, bubble and counting sort are stable as written here; selection sort, quicksort and heapsort can move an element past an equal one.
Why is the limit for comparison sorts
Any algorithm that learns about the order only by comparing pairs can be drawn as a binary decision tree: each internal node is a comparison, each leaf an output order. To sort correctly it needs a different leaf for each of the possible orderings of the input. A binary tree with leaves has height at least , so some input forces at least that many comparisons, and
For 12 elements, and , so every comparison sort needs at least 29 comparisons on its worst 12-element input. Open the comparison table under the visualiser with the input 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 to see where each algorithm lands. Counting sort escapes the bound because it is not a comparison sort: it uses the values themselves as array indices.
What real libraries use
Production sorts are hybrids that combine the strengths above.
- Timsort (Tim Peters, 2002) finds runs that are already in order, extends short ones with insertion sort, and merges them. It is stable and very fast on partly sorted data. Python’s
list.sort, Java’s sort for object arrays, and V8’sArray.prototype.sort(used in Chrome and Node.js) are Timsort or descendants of it. - Introsort starts as quicksort, switches to heapsort if the recursion gets suspiciously deep, and finishes small ranges with insertion sort, which guarantees . It is the usual implementation of C++’s
std::sort. - Dual-pivot quicksort partitions around two pivots at once; Java uses it for arrays of primitive values.
References
- C. A. R. Hoare, “Quicksort”, The Computer Journal 5(1), 1962. doi:10.1093/comjnl/5.1.10
- J. W. J. Williams, “Algorithm 232: Heapsort”, Communications of the ACM 7(6), 1964.
- D. E. Knuth, The Art of Computer Programming, Volume 3: Sorting and Searching, 2nd ed., Addison-Wesley, 1998.
- “Sorting algorithm”, Wikipedia.