Sorting Algorithms Compared
Merge sort, quicksort, heapsort and insertion sort all put things in order, but they differ in worst case, memory, stability and cache behaviour. Knowing which trade-off you are buying is the interview question, and it decides real things like how a book of orders is ranked.
Prerequisites: Big-O Complexity, Arrays and Two Pointers
Almost nothing in a trading system arrives sorted. Trades come off the wire in whatever order the exchange sent them, orders queue at a price level in arrival order, and a signal file lists instruments alphabetically when what you want is the top 50 by expected return. Sorting is the step that makes everything downstream cheap: once data is in order you can binary-search it, merge it, deduplicate it, or take the top-k in one pass.
You will rarely implement a sort in production — every language ships a very good one. But you are expected to know exactly what you are calling, because the four classic algorithms buy you different things and the wrong choice shows up as a latency spike at the worst moment.
The lower bound, and why it matters
Any algorithm that sorts by comparing pairs of elements has to distinguish between possible orderings. Each comparison gives one bit of information, so you need at least comparisons, which works out to
In words: no comparison sort can beat in the worst case. Merge sort and heapsort hit that bound; quicksort hits it on average. Sorts that beat it — counting sort, radix sort — do so by not comparing, using the key's structure instead, which only works for small integer-like keys.
The four you need to know
| Algorithm | Average | Worst | Extra space | Stable? | Use it when |
|---|---|---|---|---|---|
| Merge sort | yes | you need a guarantee and stability, or you are sorting linked/external data | |||
| Quicksort | no | in-memory arrays, speed matters, input is not adversarial | |||
| Heapsort | no | worst-case guarantee with no extra memory | |||
| Insertion sort | yes | n is tiny (under ~20) or the data is already nearly sorted |
Stable means equal keys keep their original relative order. That sounds academic until you sort orders by price and need same-price orders to stay in arrival sequence — see Stable Sorting and Tie-Breaking.
Worked example: merge sort on eight numbers
Sort [5, 2, 9, 1, 7, 3, 8, 4]. Merge sort splits until pieces are single elements, then merges sorted pieces back:
- Split into
[5, 2, 9, 1]and[7, 3, 8, 4], then split again to[5,2] [9,1] [7,3] [8,4]. - Merge each pair:
[2,5] [1,9] [3,7] [4,8]. - Merge those:
[1,2,5,9]and[3,4,7,8]. - Final merge, comparing heads: 1 vs 3 → 1; 2 vs 3 → 2; 5 vs 3 → 3; 5 vs 4 → 4; 5 vs 7 → 5; 9 vs 7 → 7; 9 vs 8 → 8; then 9. Result
[1,2,3,4,5,7,8,9].
Three levels of splitting () and work per level gives 24 units of work — the you were promised, and it never varies with the input.
Worked example: where quicksort goes wrong
Quicksort picks a pivot, partitions the array into "less than pivot" and "greater than pivot", and recurses on both sides. On [5, 2, 9, 1, 7, 3, 8, 4] with pivot 5 you get [2,1,3,4] and [9,7,8] — a near-even split, and the recursion depth is about .
Now sort [1,2,3,4,5,6,7,8] while always picking the last element as pivot. Partitioning gives an empty left side and seven elements on the right, then six, then five. That is levels of work: , on already-sorted data, which is the input you feed it most often in practice.
import random
def quicksort(a, lo, hi): # average O(n log n), worst O(n^2)
while lo < hi:
k = random.randint(lo, hi) # randomised pivot kills the sorted-input trap
a[k], a[hi] = a[hi], a[k]
p = partition(a, lo, hi)
quicksort(a, lo, p - 1) # recurse on the smaller side
lo = p + 1 # loop on the larger: O(log n) stack
Merge sort buys a worst-case guarantee plus stability for memory. Quicksort buys speed and in-place operation by accepting an worst case. Heapsort buys the guarantee for free memory but is slower in practice because it jumps around the array and misses the cache.
Use the standard library. Python's sorted and Java's Arrays.sort for objects both use Timsort: merge sort that detects already-sorted runs, so it is stable, worst-case , and linear on nearly-sorted input — which is what real timestamped market data looks like.
A broken comparator is worse than a slow sort. Your ordering must be consistent — if it says a < b and b < c it must say a < c, and it must never say two elements are simultaneously less than each other. Comparators built from floating-point differences with NaN present, or from a mutable field, silently corrupt the array or crash the sort.
Where it shows up
Interviewers ask you to name the worst case of quicksort, explain why it is still the default, define stability, and pick a sort given constraints ("sort 10 billion rows that do not fit in memory" → external merge sort; "sort ages 0–120" → counting sort). "Sort first" is also the opening move for a huge fraction of array problems — intervals, two-sum variants, and anything that then wants Binary Search Patterns.
In a trading system, matching engines keep price levels ordered and rely on stability for time priority at a level, tick replay merges per-venue files by timestamp (an external merge), and a signal pipeline that only needs the best 50 names should use Quickselect and Top-K Selection in rather than sorting all 5,000.
Related concepts
Practice in interviews
Further reading
- Cormen, Leiserson, Rivest & Stein, Introduction to Algorithms (ch. 2, 6, 7, 8)
- Sedgewick & Wayne, Algorithms, 4th ed. (Sorting)