Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Discover how sorting algorithms, from bubble to quick, merge, heap, shell, and non‑comparison methods like counting, radix, and bucket, organize data efficiently, highlighting speed, memory use, and stability.
Sorting algorithms are fundamental to computer science and programming, used to arrange data in a particular order (usually ascending or descending). At a high level, sorting refers to reorganizing a list of elements so that they follow a sequence (like numerical or lexicographical order). There are many sorting algorithms, each with different performance characteristics and use cases. Some sorting algorithms work entirely in memory (internal sorting), while others are designed to handle data that can’t fit into memory (external sorting). Internal sorting takes place within a computer’s main memory and is usually faster due to quick access times. External sorting, on the other hand, deals with massive datasets by storing and sorting parts of data on slower storage (like disks) and then merging the results, which is common in database systems and large-scale data processing.
In this article, we will explore various sorting algorithms, dividing them into two categories: Comparison-Based Sorting Algorithms and Non-Comparison-Based Sorting Algorithms. Each sorting method will be explained with its key mechanics, typical use cases, and a clear understanding of how it works. Pseudocode snippets will be provided only when necessary to clarify the process. This guide aims to be tutorial-like in tone but sufficiently clear and formal for reference. We use American English spelling and ensure WordPress-compatible formatting, including HTML entities for special characters.
Sorting algorithms are a rich area of study, with each algorithm offering different benefits and trade-offs. Comparison-based algorithms like Bubble Sort, Insertion Sort, and Selection Sort are simple and work by comparing elements, but can be slower on large inputs (typically O(n^2) time). More advanced comparison sorts like Merge Sort, Quick Sort, Heap Sort, and Shell Sort offer better performance on larger data sets, with merge and quick sorts often being the default choices for many programming libraries due to their efficiency in practice (merge sort offering stability and predictable performance, quick sort offering speed and in-place convenience on average). Non-comparison sorts like Counting Sort, Radix Sort, and Bucket Sort can achieve linear time sorting by leveraging the structure of the data values (like integer ranges or digit positions) rather than making direct comparisons. These are invaluable for special cases like sorting large numbers of integers or fixed-format data.
In practical programming, choosing the right sorting algorithm depends on the data at hand: its size, distribution, whether it fits in memory (internal vs external sorting needs), whether stability is needed, and what the performance considerations are (average case vs worst case). Understanding these algorithms allows a programmer or computer scientist to make informed decisions and to recognize opportunities to improve sorting performance in everyday tasks or specialized applications. Whether you’re implementing a sort from scratch or using a library function, knowing how these algorithms work gives insight into the cost and behavior of sorting operations in your code.
For a more technical sheet to help you implement the right sorting algorithm, check our Sorting Algorithms: A Technical Reference Sheet and Comparison Chart.
This article is part of a comprehensive guide about algorithms. To access the main article and read about other types of algorithms, from sorting to machine learning, follow the following link:
A Friendly Guide to Computer Science Algorithms by Category.
Comparison-based sorting algorithms determine order based on pairwise comparisons between elements. These algorithms compare elements, often using operators like “less than” or “greater than”, and then swap or reposition elements accordingly. They generally have a minimum time complexity of O(n log n) for comparison sorting due to theoretical limits (as proven by decision tree models). The following are well-known comparison-based sorts:
How it works: Bubble Sort is one of the simplest sorting algorithms. It repeatedly steps through the list, compares adjacent pairs of elements, and swaps them if they are in the wrong order. This “swapping” step causes larger elements to gradually “bubble up” to the end of the list while smaller elements sink towards the beginning. The algorithm makes multiple passes through the list until no swaps are needed, which indicates the list is sorted.
Bubble Sort pseudocode (for clarity):
for i from 0 to n-2:
for j from 0 to n-2-i:
if array[j] > array[j+1]:
swap(array[j], array[j+1])
This double loop ensures every pair is checked and swapped appropriately, with each outer loop pass placing the next largest element in its final position at the end.
How it works: Insertion sort builds a sorted list one element at a time. It works like sorting a hand of playing cards: take the next card (element) and insert it into the correct position among the cards already in hand (the sorted portion of the array). The array is conceptually divided into a “sorted” part and an “unsorted” part. Initially, the sorted part just contains the first element of the array, and the unsorted part contains the rest. The algorithm then picks the first element in the unsorted part and inserts it into the correct position in the sorted part, shifting larger elements to the right as needed. This process repeats for each element in the unsorted part until the entire array is sorted.
Insertion sort is in-place (needing no extra array space for many implementations) and stable (it preserves the relative order of equal elements). Here’s a brief pseudocode outline:
for i from 1 to n-1:
key = array[i]
j = i - 1
while j >= 0 and array[j] > key:
array[j+1] = array[j] // shift element right
j = j - 1
array[j+1] = key
This moves each element key
into its correct position among the previously sorted elements.
How it works: Selection sort sorts an array by repeatedly finding the minimum element from the unsorted portion and moving it to the sorted portion. It maintains two subarrays within the list: one sorted (initially empty) and one unsorted (initially the entire list). On each iteration, the smallest element (for ascending order sorting) from the unsorted subarray is selected and swapped with the first element of the unsorted subarray, expanding the sorted subarray by one and reducing the unsorted portion by one.
Note: Standard selection sort is not stable (equal elements might not preserve their input order after sorting) because it involves swaps that can move identical elements relative to each other. However, there are stable variants of selection sort that insert the element into the beginning of the unsorted array instead of swapping, though these variants typically require more complex data structures or additional memory.
How it works: Merge sort is a classic divide-and-conquer sorting algorithm. It works by dividing the array into two halves, recursively sorting each half, and then merging the two sorted halves back together. The merging process is where the core work happens: given two sorted subarrays, it efficiently combines them into a single sorted array by repeatedly picking the smaller of the two “front” elements of the subarrays and adding it to a new array.
Merge sort requires additional memory for the merging process (usually O(n) auxiliary space for arrays), although there are in-place merge sort variations that are quite complex. The straightforward implementation is not in-place.
How it works: Quicksort is another divide-and-conquer algorithm and is often the go-to algorithm for general-purpose sorting due to its excellent average performance. The algorithm picks an element as a “pivot” and partitions the array around that pivot. The partition step rearranges the array so that all elements less than the pivot come before it and all elements greater than the pivot come after it. After partitioning, the pivot element is in its final sorted position. The algorithm then recursively sorts the subarrays on either side of the pivot.
qsort
and many implementations of language-specific sort functions (like in C++ sort, older versions of Java’s sort) rely on quicksort or variations of it. However, a naive quicksort has a worst-case time complexity of O(n^2) (for instance, when the pivot is consistently chosen poorly such as always the smallest or largest element). In practice, this is mitigated by using random pivots or the “median-of-three” method, and by switching to other algorithms for small subarrays (hybrid algorithms like IntroSort start with quicksort and switch to heap sort if recursion depth is too high to avoid worst-case, and to insertion sort for tiny arrays). Real-world use cases include sorting any in-memory array or list of items by a certain key, such as sorting records by a particular field, ordering tasks by priority, etc. Quicksort is not stable by default (equal elements may be reordered relative to each other through the swapping operations).How it works: Heap sort leverages the heap data structure (often a binary heap) to sort an array. It visualizes the array as a binary tree (heap) and repeatedly extracts the largest element from the heap (for ascending order sort) and places it at the end of the array. The algorithm has two main phases: building a heap out of the input data, and then repeatedly removing the largest element from the heap and adjusting the heap to maintain the heap property (this step is often called “heapify”).
poll
the highest priority element from a priority queue you are essentially performing part of a heap sort. In practice, though, heap sort often isn’t used as a standalone sorting method in high-level libraries because quicksort (or introsort) tends to be faster on average due to better cache usage. However, understanding heap sort helps with understanding priority queues and vice versa. It is also used in cases where sorting is interwoven with other operations that naturally use a heap (like continuously receiving a stream of data and needing to emit sorted output up to the current point, etc.).How it works: Shell sort is an optimization of insertion sort that allows the exchange of far apart elements to speed up the sorting process. It works by initially sorting pairs of elements far apart from each other, then progressively reducing the gap between elements to be compared. By starting with widely spaced elements, shell sort can move out-of-place elements into correct position faster than simple adjacent swaps (as in insertion sort or bubble sort). The algorithm uses a sequence of gaps (like 5, 3, 1 or other sequences such as the Knuth sequence) and performs a gapped insertion sort for each gap value.
Shell sort is not guaranteed to run in O(n log n) for all gap sequences (in fact, finding an optimal gap sequence for all cases is still somewhat open). However, with good sequences it can approach O(n log^2 n) or better in practical scenarios.
Non-comparison sorts don’t compare elements directly to decide their order. Instead, they exploit the structure of the data (like the range of values, number of digits, etc.). These algorithms can achieve linear time complexity O(n) under certain conditions, making them extremely fast for specific use cases. The trade-off is that they often require additional memory and are not as general-purpose (they might only work for integers or have constraints on the data values). Key non-comparison sorts include Counting Sort, Radix Sort, and Bucket Sort.
How it works: Counting sort is an integer sorting algorithm that operates by counting the occurrences of each value in the input array. It assumes the input consists of integers within a known range (for example, 0 to 100, or 1 to K). The algorithm creates an auxiliary array (often called count
) of length equal to the range of input values. It then iterates through the input array and for each element, it increments the count at the index corresponding to that element’s value. After counting all elements, it then modifies the count array such that each element at each index in the count array contains the sum of the previous counts; this step effectively gives the positions of the values in the sorted order. Finally, it iterates through the input array again (often in reverse order to maintain stability) and places each element into its correct sorted position in an output array, decreasing the corresponding count each time an element is placed.
count[v]++
. Now, count[i]
tells us how many times i appears. The next step is to transform this count array into a prefix sum array: for each i
from 1 to 50, do count[i] = count[i] + count[i-1]
. After this, count[i]
will tell us how many elements are <= i. If we have an output array, we can then place each element x from the input array to its sorted position by looking up count[x]
(which tells us the last index where x should appear in the sorted array), placing x there, and then decrementing count[x]
to indicate that position is now filled.Also, counting sort operates in O(n + K) time where n is the number of elements and K is the range of input values. This can be O(n) if K is in the same order as n (for example, K = 100 and n = 1000, etc.). The absence of comparisons allows it to break the typical O(n log n) barrier that comparison sorts cannot cross.
How it works: Radix sort sorts numbers (or other data that can be represented in digits, like strings or dates) by processing individual digits. It breaks the sorting task into sorting by each digit position. There are two main variants: Least Significant Digit (LSD) radix sort and Most Significant Digit (MSD) radix sort. The LSD approach sorts the array repeatedly by each digit from the least significant (rightmost) digit to the most significant digit, using a stable sort (like counting sort) at each digit position. The MSD approach starts from the most significant digit and works inward.
Radix sort relies on a stable intermediate sort (like counting sort or bucket sort) at each digit position to maintain correct ordering, and it also uses additional memory to store output of each pass. It’s not a comparison sort, and its efficiency comes from exploiting the representation of the keys. Radix sort is great for situations like:
How it works: Bucket sort (sometimes called bin sort) works by distributing elements into several “buckets” and then sorting those buckets individually. It is typically used for sorting numbers that are uniformly distributed in a range. The algorithm divides the range of possible values into a number of intervals (buckets), then it assigns each element of the array to one of these buckets based on its value. After distributing all elements into buckets, each bucket is sorted (often using another sorting algorithm, which could be a simple method like insertion sort or even recursively applying bucket sort), and finally, the buckets are concatenated in order to form the sorted array.
Example: If you have to sort 1 million uniformly distributed random numbers between 0 and 100, you might choose 1000 buckets covering ranges of 0.1 each. Each bucket would on average get about 1000 elements (since 1,000,000/1000 = 1000). If you then sort each bucket with an O(m log m) algorithm (where m is 1000 in this average case), the total work is roughly 1000 * (1000 log 1000), which is much less than 1,000,000 log 1,000,000 for a typical comparison sort. Thus, bucket sort can be very efficient if the distribution is favorable.
One important note: bucket sort can be stable or unstable depending on how the buckets are handled and how the sort inside each bucket is performed. If stability matters (preserving input order for equal keys), one should use a stable sorting algorithm within the buckets or ensure that items going into buckets already maintain an order if necessary.
To go to the main guide about algorithms follow this link: A Friendly Guide to Computer Science Algorithms by Category,