9 Sorting Algorithms
A structured guide to comparison-based sorting algorithms, their complexity, stability, memory use, and practical selection criteria.
Foundations of comparison sorting
Sorting rearranges items according to a specified ordering, such as ascending numerical order or alphabetical order. Once data is ordered, binary search becomes possible, duplicate values are easier to detect, and reports can be produced in a predictable sequence.
The algorithms in this guide are comparison-based: they determine order by comparing pairs of elements. For arbitrary input, comparison-based sorting cannot guarantee a worst-case running time better than . Simpler quadratic algorithms remain useful because they are easy to implement, effective on small inputs, or efficient when the data is already nearly ordered.
Two questions organize the analysis of every sorting algorithm:
How does its running time grow with input size ?
What extra memory does it need, and does it preserve the order of equal keys?
A useful first distinction is between quadratic growth, , and the usual efficient comparison-sorting target, . Some algorithms can achieve on especially favorable inputs, but that does not make their general performance linear.
Takeaway: Sorting is not only about producing the right order; algorithm choice depends on growth rate, memory, input order, and treatment of equal keys.
How to evaluate a sorting algorithm
Three properties are particularly important when comparing sorting methods.
should be reported for the best case, average case, and worst case. For example, an algorithm may run in on an already sorted array but still require time on a difficult input.
means that the algorithm uses only a small amount of additional memory beyond the input array. This description does not necessarily include the recursion stack, which should be reported separately when it matters.
means that records with equal keys retain their original relative order. For example, if the input contains before , a stable sort keeps the first record before the second after sorting by last name. supports multi-key workflows, such as sorting employees by department and then by salary.
A compact comparison of the main algorithms is:
: stable and in-place; best case with early termination, but average and worst cases .
: in-place and usually not stable; in every listed time case.
: stable and in-place; best case , average and worst cases .
: stable with the usual merge rule; in every listed time case, but typically needs extra array space.
: usually in-place and typically not stable; average case , but worst case .
Takeaway: No single complexity label is sufficient. Always connect time bounds with space requirements, , and the assumptions behind the best or average case.
: adjacent exchanges
repeatedly compares adjacent elements. When the left element is greater than the right element, the algorithm swaps them. After one complete pass, the largest element in the remaining unsorted portion has moved to the end of that portion.
For the list , the first comparisons produce:
Compare and , then swap: .
Compare and , then swap: .
Compare and , then swap: .
The next pass can ignore the final position because it is already correct. An early-exit flag stops the algorithm when a full pass makes no swaps. Consequently, the best case is when the input is already sorted, while the average and worst cases are . uses extra space and is stable when it swaps only strictly out-of-order adjacent elements.
Takeaway: is useful for illustrating adjacent exchanges and early termination, but it is rarely suitable for large inputs.
: choosing the next minimum
maintains a sorted prefix and an unsorted remainder. On each pass, it scans the remainder to find its smallest element, then swaps that element with the first unsorted position.
For , the first pass finds and produces . The next smallest value is , which is already in place. The final remaining adjustment produces .
always scans the unsorted portion, even when the array is already sorted. Therefore, its best, average, and worst-case running times are all . It uses extra space and performs only about swaps, which can be valuable when writes are expensive. The standard implementation is not stable because a long-distance swap can change the relative order of equal keys.
Takeaway: Choose when minimizing swaps matters more than minimizing comparisons, not when the input is large or nearly sorted.
: exploiting existing order
grows a sorted prefix one item at a time. It stores the next value, shifts every larger prefix element one position to the right, and inserts the stored value into the gap.
For :
Insert before , giving .
Insert ; it is already in the correct position.
Shift , , and , then insert , giving .
On an already sorted or nearly sorted array, few shifts are needed, so the best case is . The average and worst cases are , with reverse-sorted input providing a difficult case. uses extra space and is stable when equal elements are not moved past one another.
An measures a pair that is in the wrong relative order. performs shifts closely related to the number of inversions, which explains why it performs well when the input has only a few such pairs.
Takeaway: is a strong simple choice for small arrays, nearly sorted data, and small subarrays inside more complex sorting strategies.
: divide, solve, and merge
applies divide and conquer. It divides the array into two halves, recursively sorts each half, and merges the two sorted results. The merge step repeatedly chooses the smaller first remaining element from the two halves.
For , the process can be summarized as:
Divide into and .
Divide again into single-element arrays: , , , and .
Merge to obtain and .
Merge those results to obtain .
Each recursion level processes all elements during merging, and there are approximately levels. Thus the best, average, and worst-case running times are all . The usual array implementation uses auxiliary space. It is stable when the merge chooses the element from the left subarray first whenever the two compared keys are equal.
Takeaway: trades additional memory for predictable performance and stable ordering.
: partitioning around a
also uses divide and conquer, but it divides the array through partitioning rather than by fixed halves. It chooses a , rearranges the elements so that smaller values lie on one side and larger values lie on the other, and then recursively sorts the two resulting partitions.
For , choosing as the can produce after partitioning. The is in its final position, while the subarrays and still require sorting.
When partitions are reasonably balanced, runs in . Repeatedly unbalanced partitions can produce worst-case time, especially with a poor strategy on already sorted or specially arranged input. In-place partitioning generally uses extra array space, but the recursion stack requires space on average and can require in the worst case. Standard implementations are not stable.
Random shuffling, randomized pivots, median-of-three selection, and switching to for tiny subarrays are common ways to improve practical behavior.
Takeaway: can be fast and memory-efficient, but its performance depends strongly on controlling partition balance.
Choosing the right algorithm
Algorithm selection should reflect the input and the required guarantees rather than relying on one universal ranking.
Choose for small arrays or data that is already nearly sorted.
Choose when the number of writes or swaps must be kept low and quadratic comparisons are acceptable.
Choose when stable ordering and a guaranteed bound are important, and additional memory is available.
Choose when average-case speed and low extra array memory are priorities, provided selection or randomization controls worst-case risk.
Treat primarily as a teaching tool or as a solution for very small, simple inputs.
For production software, a trusted library sort is usually preferable to a new hand-written implementation. Library algorithms can combine strategies, use carefully engineered partitioning, and exploit existing order. and memory behavior should still be checked because different libraries make different guarantees.
A practical decision process is:
Ask whether stable ordering is required.
Estimate the input size and how ordered the data already is.
Check whether additional memory is available.
Decide whether worst-case guarantees or average-case speed matter more.
Prefer a reliable library implementation when one satisfies the requirements.
Takeaway: The best sorting algorithm depends on input size, existing order, memory limits, requirements, write costs, and acceptable worst-case behavior.
Key conclusions
The main ideas can be connected as follows:
Sorting makes later operations easier by imposing an order.
Comparison-based sorting has a general worst-case lower bound of .
and can achieve best cases, but both have quadratic average and worst cases.
is quadratic in every listed time case, but it uses constant extra space and relatively few swaps.
guarantees time and can be stable, at the cost of typically requiring auxiliary memory.
often performs well with balanced partitions and low array overhead, but poor choices can cause time and a large recursion stack.
, in-place behavior, and complexity describe different properties; an algorithm can be strong in one and weak in another.
When comparing algorithms, state the assumptions behind each claim. For example, 's linear best case depends on early termination, and 's average-case bound depends on suitable behavior or randomization.