Hello! In our last lesson, we dove deep into Quick Sort, exploring both the Lomuto and Hoare partitioning schemes. We saw how its average-case performance is exceptionally fast, making it a go-to sorting algorithm, but we also highlighted its O(n²) worst-case on sorted data when using a naive pivot strategy.
A key theme so far is that all the sorting algorithms we've discussed—Merge Sort, Quick Sort, etc.—are "comparison-based." They sort by comparing pairs of elements, and for this reason, they can't perform better than O(n log n) in the average case. Today, we break that barrier.
This lesson introduces Counting Sort, our first non-comparison sorting algorithm. By making a crucial assumption about the input data, it achieves a remarkable linear time complexity. Your goal for this lesson is to learn how to apply Counting Sort to problems where the input is a collection of integers within a limited range, a common scenario in competitive programming and interview problems.
1. The Core Idea: Sorting by Counting
Imagine you need to sort an array of integers where you know all numbers are between 0 and 9. Instead of comparing numbers to each other, what if you just counted how many 0s, 1s, 2s, etc., are in the array?
If you find there are two 0s, one 3, and four 5s, you can immediately reconstruct the sorted array: [0, 0, 3, 5, 5, 5, 5].
This is the fundamental principle of Counting Sort. It leverages the element values themselves as indices into a helper array to store counts, completely sidestepping the need for comparisons.
This simple version of the algorithm involves two main phases:
- Counting Phase: Create a
countarray (often called a frequency array). Iterate through the input array and for each elementx, incrementcount[x]. - Reconstruction Phase: Iterate through the
countarray from the beginning. For each indexi, write the valueiinto the original arraycount[i]times.
To see this simple but powerful idea in action, let's watch a short video that walks through the logic and a basic implementation.
Count Sort Algorithm - Theory + Code
The video 'Count Sort Algorithm - Theory + Code' by Kunal Kushwaha provides a clear, concise explanation of this basic approach.
Please watch the following two segments: The Core Idea (1:37 - 6:23): Focus on how the presenter uses a frequency array to count the occurrences of each number and then uses that frequency array to rebuild the sorted list. Java Implementation (11:21 - 16:03): Observe how this logic is translated into a straightforward Java function. Notice the three main parts: finding the max value, populating the frequency array, and then rewriting the original array.
This approach is intuitive and works perfectly for sorting primitive integers. Here’s a Java implementation reflecting what you just saw.
// Simplified (unstable) Counting Sort for non-negative integers
public void simpleCountSort(int[] arr) {
if (arr == null || arr.length <= 1) {
return;
}
// 1. Find the maximum value to determine the size of the count array
int max = 0;
for (int num : arr) {
if (num > max) {
max = num;
}
}
// 2. Create the count (frequency) array
int[] count = new int[max + 1];
for (int num : arr) {
count[num]++;
}
// 3. Reconstruct the sorted array
int arrIndex = 0;
for (int i = 0; i <= max; i++) {
while (count[i] > 0) {
arr[arrIndex] = i;
arrIndex++;
count[i]--;
}
}
}
This implementation is simple and efficient. However, it has a limitation: it's not stable. A stable sort preserves the original relative order of equal elements. This property isn't important for primitive integers, but it's crucial when sorting objects with multiple properties.
2. The General & Stable Counting Sort
For interviews and more complex problems, you'll need the general, stable version of Counting Sort. This version can handle sorting objects by an integer key while preserving their original order if the keys are the same. It requires a few more steps and a separate output array.
Here’s the refined algorithm:
- Create Frequency Array: Same as before, create
count[k+1]and populate it with frequencies. - Create Position Array: Modify the
countarray so that each elementcount[i]stores the sum of counts up toi. After this step,count[i]tells you the position in the sorted array where the last element with valueishould go. This is also known as a cumulative frequency array. - Build Sorted Array: Create a new
outputarray of the same size as the input. Iterate through the input array from right to left. For each elementx:
a. Find its target position from the cumulativecountarray:pos = count[x] - 1.
b. Placexin the output array:output[pos] = x.
c. Decrement the count for that value:count[x]--. This ensures that the next element with the same value is placed one position to its left. - Copy Back: Copy the sorted elements from the
outputarray back to the original array.
The backward iteration is the key to ensuring stability. Let's study an article that explains this process with excellent diagrams and code.
The article 'Counting Sort in Java' from Baeldung provides a superb, step-by-step guide to the stable version of the algorithm.
Please read sections '2. Counting Sort', '3. Counting Sort – Java Implementation', and pay special attention to the visual breakdown. Focus on: How the initial 'Frequency Array' is transformed into a cumulative sum array. In 'The Algorithm' subsection, how the reverse iteration places elements into their correct final positions. The clean Java code provided in '3.1' and '3.2', which clearly separates the logic for creating the count array and performing the sort.
To solidify your understanding, here is a complete visual summary of the stable algorithm.

Test your understanding!
Why does iterating through the input array from right-to-left in the stable version ensure stability?
Consider the input array [(value=3, id='A'), (value=2, id='B'), (value=3, id='C')]. The id represents the original order. What would happen if you iterated from left-to-right?
Show answer
If we iterate from left-to-right:
- The first element
(3, 'A')is processed. Let's say the cumulative count for3isp. It gets placed at positionp-1in the output. The count for3is decremented. - The last element
(3, 'C')is processed. It gets placed at the new, decremented positionp-2. - The final sorted order for the 3s would be
[(3, 'C'), (3, 'A')]. The original order ('A' before 'C') is reversed.
By iterating from right-to-left:
- The last element
(3, 'C')is processed first. It gets placed at positionp-1. The count is decremented. - The first element
(3, 'A')is processed later. It gets placed at the new positionp-2. - The final sorted order is
[(3, 'A'), (3, 'C')], which preserves their original relative order.
3. Complexity and Practical Considerations
Time and Space Complexity
-
Time Complexity: O(n + k)
- O(n) to iterate through the input array to find the max value.
- O(n) to populate the frequency counts.
- O(k) to calculate the cumulative sums.
- O(n) to build the sorted output array.
- Total: O(3n + k) which simplifies to O(n + k), where
nis the number of elements andkis the range of the input values (max - min + 1). - This is linear time when
kis on the order ofn(i.e.,k = O(n)).
-
Space Complexity: O(n + k)
- O(k) for the
countarray. - O(n) for the temporary
outputarray in the stable version.
- O(k) for the
Handling Negative Numbers
A common interview follow-up is: "How would you adapt this to handle negative numbers?" The array-indexing approach fails with negative indices. The solution is to use an offset.
- Find both the
minandmaxvalues in the input array. - The size of the
countarray will bemax - min + 1. - When counting, the index for a number
xwill bex - min. - When reconstructing the array, the value to be written is
index + min.
Here's a code snippet illustrating the idea:
// Snippet for sorting arrays with negative numbers
public void countingSortWithNegatives(int[] arr) {
if (arr.length == 0) return;
int max = arr[0], min = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) max = arr[i];
if (arr[i] < min) min = arr[i];
}
int range = max - min + 1;
int[] count = new int[range];
// Phase 1: Count using offset
for (int num : arr) {
count[num - min]++;
}
// Phase 2: Modify to cumulative counts (for stable sort)
for (int i = 1; i < range; i++) {
count[i] += count[i - 1];
}
// Phase 3 & 4 would proceed similarly, using the offset
// when accessing the count array and converting back.
}
Conclusion
You've just added a powerful linear-time sorting algorithm to your toolkit. While not a universal replacement for Quick Sort or Merge Sort, Counting Sort is unbeatable for its specific use case and is a favorite in coding challenges for its simplicity and speed.
Key Takeaways:
- Counting Sort is a non-comparison algorithm with a time complexity of O(n + k).
- It's highly effective when the range of input integers (
k) is not significantly larger than the number of elements (n). - The simple implementation is intuitive but not stable.
- The stable implementation is essential for general-purpose sorting of objects and is achieved by using a cumulative frequency array and iterating backward through the input.
- Handling negative numbers is a common extension, solved by using an offset (
minvalue).
Preview of the next lesson:
We've covered a variety of sorting algorithms, each with its own trade-offs. Now, we'll shift our focus from sorting to another fundamental problem area: searching within strings. In the next lesson, we will explore string hashing and the Rabin-Karp algorithm, a clever technique that uses a "rolling hash" to find patterns in text efficiently. This is another crucial tool for your competitive programming and algorithm design arsenal.