Counting sort runs on a different idea than comparison based sorting and treats data as values placed within a known span of integers. It tallies how many times each value appears, and that count guides where everything lands in the final array. The method moves fast when the numeric span stays small and shows how frequency based work replaces element to element checks. It also fits in the wider sorting family by keeping linear time in the right cases while holding stable placement for equal values.
Window Into Counting Mechanics
Sorting with counts moves through a set of steps that feed into one another, and the whole idea feels smoother once those steps fall into place. Comparisons never enter the picture. The method relies on tallies and simple passes that push values toward their final spots. A wide or narrow range both follow the same flow, and the values naturally guide their own positions after the tallies form.
Frequency Tallies As The Core
Counting begins with a pass through the input that builds tallies for every value inside the known span. Each tally lands at an index tied to the value itself. That gives the method a compact picture of how the data spreads out, and it spares any back and forth checks between two values. After those tallies settle, the method already knows how many spaces to reserve for every number that appears.
This scan can show this visually:
The tallies in that array reflect how the values spread through the input. Some cases call for finding the maximum first so the count array matches the range.
The flow stays the same for smaller or larger arrays. Nothing in this step leans on outdated Java features, and the logic stays practical across current runtimes. Some situations work better if the minimum is tracked as well, which keeps the count array tight when values don’t start at zero.
This version sticks to the same idea but keeps the tally range compact. Tallies form the base for the rest of counting mechanics, and the later stages only need these numbers to build exact positions.
Prefix Totals For Placement
Tally counts only tell how many times a value appears. A prefix sweep gives those counts meaning by turning them into boundaries inside the final array. This sweep moves through the count array from left to right, and each slot becomes the total for everything up to that point.
Those accumulated counts form a map of where values should land in sorted order. Each index becomes the upper line for placements, and every value in the input will draw from that number during the final pass.
A shifted range uses the same update:
These totals guide the rest of the method without introducing comparisons. The prefix stage gives the method a way to drop values into place with confidence that every position lines up correctly.
Stable Placement Through Backward Movement
The last stage walks backward through the input and drops values into the output array using the prefix totals. Working from the end keeps equal values in their original order, because later arrivals settle later in the final result. Stability matters in many real workflows, and counting sort reaches that result with a single backward sweep.
The placement step looks like this:
This pass trims the prefix boundary for the value after every placement. Equal values fall into the correct sequence without side steps or extra storage.
Narrowed spans of values follow the same idea with a small adjustment:
The two versions both protect stability. The backward walk gives equal values room to land in the right order with minimal effort. Counting sort closes with a full picture of the sorted array, and every step from tallies through placement remains grounded in modern Java practice.
How Counting Sort Fits Among Other Methods
Sorting has a long family tree, and counting sort holds a spot that feels different from the comparison based group. Many people are used to methods that weigh two values side by side to judge order. Counting sort skips that idea and instead works with tallies and known spans, which places it in a separate corner of the sorting world.
Comparison Free Sorting
Counting sort builds its ordering without putting two values head to head. That alone draws a noticeable line between it and methods like mergesort or heapsort. Those methods depend on checks that decide which value deserves to move forward at every turn. Counting sort instead treats the input as a pool of numbers that fall into a known span and then tallies how often each one appears. That gives the method the freedom to build the sorted array directly from those tallies without ever pairing two values.
Take this basic example:
The next pass creates the sorted result without a single comparison.
This avoids stability handling, but it shows the concept with total clarity. Comparison based methods need decision making at every small turn, while counting sort moves with tally data alone. This difference places the method in a category where its speed depends on the data span far more than the number of elements.
Memory And Span Limits
Counting sort thrives when values fall into a narrow span. That’s because the method allocates a frequency array that covers the full distance from the smallest value to the largest one. A large span doesn’t always reflect how many numbers sit in the input, so it’s possible for the method to build a frequency array far bigger than the input itself. That mismatch can make other sorting methods a better pick for wide numeric ranges.
A short example builds out this scenario. Suppose a dataset holds a small set of values scattered across a large span:
If the code follows counting sort rules, the next step allocates based on the full span.
This array may cover thousands of slots even if the input holds only a handful of values. Counting sort still works, but the tradeoff becomes obvious. Memory use grows in direct relation to the numeric span, not the dataset size. Methods like mergesort avoid that tight bond because they never depend on knowing the numeric spread.
Still, when the value span stays small, counting sort moves fast and predictably. Its memory footprint remains compact, and the tallies build in a tight range that fits well with Java’s array behavior across all modern versions.
How Stability Matters For Later Steps
Stable ordering gives counting sort an advantage in workflows that depend on multi pass sorting. A stable method keeps the original order for values that match, which lets earlier ordering rules survive into later stages. That fact becomes vital in methods like radix sort, where the data moves through several passes that sort by individual digits or fields.
One common case shows how this property supports later passes. Suppose an object has two fields and the code sorts by the second field first, then by the first field. Stability makes that order stick.
The first pass can sort by grade through counting sort:
This counting pass by grade keeps the original order among items with grade 1. A later pass that sorts by group can then rely on the stable ordering to preserve the earlier placement tied to grade, giving a final result that reflects both fields in a predictable way.
Stability adds value without extra machinery. Counting sort secures that property through its backward placement step, and the effect ripples outward into later stages where ordering needs to flow from one rule into the next.
Comparison With Linear Time Relatives
Counting sort shares the linear time space with methods that also step around comparisons. Bucket sort spreads values across ranges, and radix sort breaks values into digits processed one level at a time. All three methods take advantage of structural clues in the input rather than comparisons.
Radix sort depends heavily on stability for accuracy. Counting sort often serves as a building block in that world, giving it a deeper place among non comparison sorts. One pass of radix sorting might call counting sort under the hood for a digit based sweep. Java code frequently pairs them in teaching and production settings because the two fit comfortably side by side in this structure.
A digit based pass can look like this:
The follow up prefix sweep and backward placement step mirrors counting sort’s structure. Radix sort evolves from this process by repeating passes at higher digit places.
Sorting with tallies holds a steady place in this group because it delivers linear performance when the span stays narrow and keeps stability without extra work. Its position feels natural among the small family of sorts that drop comparisons and rely on structure already present in the data.
Conclusion
Counting sort turns simple tallies into a full ordering of values without placing elements side by side for comparisons. Tallies guide the structure, prefix totals steer placements, and the backward pass keeps equal values in their original flow. All three pieces fit smoothly with modern Java and stay fast when the value span stays narrow. The method fits naturally in the family of non comparison sorts, supported by tallies, placement ranges, and stable movement from start to finish.


![int[] input = {5, 1, 5, 2, 1, 3}; int[] count = new int[6]; for (int v : input) { count[v]++; } int[] input = {5, 1, 5, 2, 1, 3}; int[] count = new int[6]; for (int v : input) { count[v]++; }](https://substackcdn.com/image/fetch/$s_!L2yV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F98265846-d4ed-4016-99d2-7db4a82c75b9_1500x250.png)
![int max = 0; for (int v : input) { if (v > max) { max = v; } } int[] count2 = new int[max + 1]; for (int v : input) { count2[v]++; } int max = 0; for (int v : input) { if (v > max) { max = v; } } int[] count2 = new int[max + 1]; for (int v : input) { count2[v]++; }](https://substackcdn.com/image/fetch/$s_!jmBD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92bd5706-7721-4d7f-9cd6-203d28367775_1478x460.png)
![int min = input[0]; for (int v : input) { if (v < min) { min = v; } } int[] localCount = new int[max - min + 1]; for (int v : input) { localCount[v - min]++; } int min = input[0]; for (int v : input) { if (v < min) { min = v; } } int[] localCount = new int[max - min + 1]; for (int v : input) { localCount[v - min]++; }](https://substackcdn.com/image/fetch/$s_!Isie!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90df3aeb-9509-40c8-870d-368a2a4c93f9_1484x464.png)
![for (int i = 1; i < count.length; i++) { count[i] += count[i - 1]; } for (int i = 1; i < count.length; i++) { count[i] += count[i - 1]; }](https://substackcdn.com/image/fetch/$s_!0tzs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda4cfe2e-7786-4997-98b0-533803bdd93c_1497x131.png)
![for (int i = 1; i < localCount.length; i++) { localCount[i] += localCount[i - 1]; } for (int i = 1; i < localCount.length; i++) { localCount[i] += localCount[i - 1]; }](https://substackcdn.com/image/fetch/$s_!VOGl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6003dcd5-d753-494d-85e7-dfa5c4df7a82_1495x129.png)
![int[] output = new int[input.length]; for (int i = input.length - 1; i >= 0; i--) { int v = input[i]; int pos = count[v] - 1; output[pos] = v; count[v]--; } int[] output = new int[input.length]; for (int i = input.length - 1; i >= 0; i--) { int v = input[i]; int pos = count[v] - 1; output[pos] = v; count[v]--; }](https://substackcdn.com/image/fetch/$s_!Tl3n!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3d8012b-a101-4819-aa04-4ad12d71d50f_1469x339.png)
![int[] sorted = new int[input.length]; for (int i = input.length - 1; i >= 0; i--) { int v = input[i]; int idx = v - min; int pos = localCount[idx] - 1; sorted[pos] = v; localCount[idx]--; } int[] sorted = new int[input.length]; for (int i = input.length - 1; i >= 0; i--) { int v = input[i]; int idx = v - min; int pos = localCount[idx] - 1; sorted[pos] = v; localCount[idx]--; }](https://substackcdn.com/image/fetch/$s_!JLWB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02ce31b8-9b12-462c-8591-db78aba760bc_1478x383.png)
![int[] grades = {3, 1, 4, 1, 2}; int maxGrade = 4; int[] tallies = new int[maxGrade + 1]; for (int g : grades) { tallies[g]++; } int[] grades = {3, 1, 4, 1, 2}; int maxGrade = 4; int[] tallies = new int[maxGrade + 1]; for (int g : grades) { tallies[g]++; }](https://substackcdn.com/image/fetch/$s_!N0XD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02d555aa-e950-4e4c-a5ed-d47433277639_1500x297.png)
![int[] sortedGrades = new int[grades.length]; int pos = 0; for (int v = 0; v < tallies.length; v++) { for (int c = 0; c < tallies[v]; c++) { sortedGrades[pos++] = v; } } int[] sortedGrades = new int[grades.length]; int pos = 0; for (int v = 0; v < tallies.length; v++) { for (int c = 0; c < tallies[v]; c++) { sortedGrades[pos++] = v; } }](https://substackcdn.com/image/fetch/$s_!K2ac!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe34a79e9-8ef3-430c-be1f-719bcac7f864_1499x294.png)
![int[] ids = {102, 9005, 501, 9005}; int minId = ids[0]; int maxId = ids[0]; for (int id : ids) { if (id < minId) { minId = id; } if (id > maxId) { maxId = id; } } int[] ids = {102, 9005, 501, 9005}; int minId = ids[0]; int maxId = ids[0]; for (int id : ids) { if (id < minId) { minId = id; } if (id > maxId) { maxId = id; } }](https://substackcdn.com/image/fetch/$s_!PV4v!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febfcc1b0-738f-4d4f-b52e-16c57623f91c_1476x506.png)
![int span = maxId - minId + 1; int[] freq = new int[span]; int span = maxId - minId + 1; int[] freq = new int[span];](https://substackcdn.com/image/fetch/$s_!oTIj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febf99976-0a74-4ea8-aea8-19fba0c9628a_1498x83.png)

![Record[] data = { new Record(2, 1), new Record(1, 1), new Record(3, 2) }; Record[] data = { new Record(2, 1), new Record(1, 1), new Record(3, 2) };](https://substackcdn.com/image/fetch/$s_!NKdo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d3358a2-2331-466f-9b86-945eed4928c1_1496x210.png)
![int[] nums = {329, 457, 657, 839, 436, 720, 355}; int place = 1; int[] bucket = new int[10]; for (int n : nums) { int digit = (n / place) % 10; bucket[digit]++; } int[] nums = {329, 457, 657, 839, 436, 720, 355}; int place = 1; int[] bucket = new int[10]; for (int n : nums) { int digit = (n / place) % 10; bucket[digit]++; }](https://substackcdn.com/image/fetch/$s_!iRtq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F105a94a5-a5e8-4cbc-b163-a7e780c9fc2c_1482x335.png)