How do you choose between sorting algorithms for a given input?
Start with the data, not the algorithm. Ask four questions: is the data already partially ordered, how many elements are there, how much extra memory can you spend, and does the key have a small numeric range? Those four answers rule out most options immediately.
def merge_sort(values):
if len(values) <= 1:
return list(values)
mid = len(values) // 2
left = merge_sort(values[:mid])
right = merge_sort(values[mid:])
merged = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
merged.append(left[i]); i += 1
else:
merged.append(right[j]); j += 1
return merged + left[i:] + right[j:]
data = [5, 2, 9, 1, 7]
print(merge_sort(data))
print(sorted(data))The outputs agree: 1, 2, 5, 7, 9. The hand-written merge sort splits the array in half, sorts each half and merges them, which is why its cost does not depend on the input order.
The mechanism behind the choices is that every comparison sort needs logarithmically many rounds of comparisons in the worst case, because a comparison can only split the possibilities in two. So all of them sit at O of n log n, and the differences between them are about constants, stability and space. A non-comparison sort such as counting sort breaks that lower bound by exploiting the structure of the keys, which is why it becomes O of n plus k.
The trade-off to name is the choice of failure mode. Merge sort guarantees its bound and needs linear space; quicksort is in place and fast but can degrade to quadratic; heapsort is in place with a guaranteed bound but has poor cache behaviour; counting sort beats the comparison bound but only for small integer keys. In production the honest answer is usually the library sort, because Python's Timsort is stable, adaptive to existing runs, and implemented in C.