Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Each item is a pair `[key, value]` where the integer key is in 0..99 and the value is a string. Replace the string of every item in the FIRST HALF (index `i < n/2`) with '-', then stably counting-sort the items by key, and print the resulting strings space-separated. Stability is the crux: items with the same key must keep their original input order, which is exactly what bucketing-in-input-order (or prefix-sum placement) gives you.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you implement counting sort *stably* while carrying satellite data, and whether you correctly apply the index-based '-' transformation using 0-based n/2.
Where candidates slip
Losing stability (e.g. placing by reverse scan with forward-increment, or rebuilding from counts only), or replacing the wrong half / using 1-based indexing for the '-' rule.
How to stand out
Call out that appending to buckets while scanning forward is inherently stable, contrast with the prefix-sum + back-to-front placement formulation, and connect stability to radix sort correctness.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewItem i = (x, s)i < n/2 ?YesNoExplore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2 · Optimal
Bucket Lists in Input Order (Stable, Optimal)
1Hint 1Explore hintHide hint
Each item has a small integer key (0..99) and a string. How would you group strings by key without comparing keys?
2Hint 2Explore hintHide hint
Keep 100 lists. Scanning left to right, append each (possibly dashed) string to the list for its key.
3Hint 3Explore hintHide hint
Because you append in input order, equal keys stay ordered automatically. Concatenate lists 0..99 and join with spaces.
A · Core idea
This is counting sort generalised to records. The integer is the sort key; the string rides along.
Step 1 — the '-' transform. Scan items with their 0-based index i. If i < n/2 (integer division), the string is replaced by '-'; otherwise it is kept. This must use the original index, so do it during the same scan that buckets the items, before any reordering.
Step 2 — bucket by key, in input order. Maintain 100 lists (one per possible key). Append each item's string to bucket[key]. Crucially, because we scan the input left to right and append, two items with the same key land in their bucket in the same relative order they appeared — that is stability, for free, with no extra work.
Step 3 — emit ascending. Walk buckets 0..99 and concatenate. Smaller keys come first; within a key, input order is preserved. Join the strings with single spaces and print.
Why not rebuild from counts? Counting Sort 2's 'expand a histogram' trick regenerates the value, which would lose the attached string. To carry payloads you must store the actual strings — per-key lists (or prefix-sum placement) are the way.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
n = 4 → [(1,"a"), (0,"b"), (1,"c"), (0,"d")]Array state
after scan
bucket0=['-','d'], bucket1=['-','c']
- d - cC · Code with teaching comments
D · Why it works and complexityO(n + k) timeO(n + k) spaceOpen the invariant and proofExplore the proofHide the proof
Appending to a per-key list while scanning forward is the simplest correct stable counting sort. Stability is not an add-on here — it falls out of the append order — and that same stability is what makes a multi-pass radix sort produce a correct final ordering.
ComplexityO(n + k) time, O(n + k) space — One pass to bucket all n strings is O(n); concatenating across k = 100 buckets touches each string once more, O(n + k) total time and O(n + k) space for the buckets and output.
E · Edge cases and pitfalls5all same key — One bucket holds every string in input order → output equals the (dashed) input order.Explore edge casesHide edge cases
Edge cases handled
all same keyOne bucket holds every string in input order → output equals the (dashed) input order.n = 2, both key 0n/2 = 1 → index 0 dashed, index 1 kept; output '- <s1>'.
Pitfalls
- Replacing strings using 1-based index or comparing i <= n/2 — off by one on the half boundary.
- Sorting the (key, string) pairs with a comparison sort that is not stable — equal keys reorder.
- Printing keys instead of strings, or forgetting single-space separation.
Approach 2 of 2
Prefix-Sum Stable Placement
1Hint 1Explore hintHide hint
Count items per key, then turn counts into starting output indices with a prefix sum.
2Hint 2Explore hintHide hint
To stay stable while placing into a flat array, scan the input from the LAST element to the FIRST and decrement the position for each key.
3Hint 3Explore hintHide hint
Carry the string into the computed slot so the payload travels with its key.
A · Core idea
Same result without dynamic per-key lists — the classic CLRS counting sort, made stable.
Count, then prefix-sum. Tally count[key] over all items. Convert to ending positions: pos[key] = number of items with key ≤ key. Now pos[key]-1 is the last output index for that key.
Place from the back. Iterate items from index n-1 down to 0. For each item, decrement pos[key] and write the (dashed-or-kept) string at that index. Scanning backward with a decrementing end-position keeps earlier-input equal keys at earlier output indices — stable.
Why backward + end-positions = stable. The last occurrence of a key is placed at the highest slot of its block first; as we move toward the front of the input, each earlier equal-key item takes the next-lower slot. The net effect preserves input order within each key. (Equivalently, use start-positions and scan forward.)
Why it matters. This formulation is exactly one digit-pass of LSD radix sort. Chaining stable passes from least- to most-significant digit sorts large keys in linear time — and it only works because each pass is stable.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
[(1,a),(0,b),(1,c),(0,d)] dashed first half → [(1,'-'),(0,'-'),(1,c),(0,d)]Array state
final
out = ['-', d, '-', c]
- d - cC · Code with teaching comments
Reference implementation previewpublic String fullCountingSortStable(int[] keys, String[] vals) { int n = keys.length; String[] payload = new String[n]; for (int i = 0; i < n; i++) { payload[i] = (i < n / 2) ? "-" : vals[i];Explore the full codeHide the full code
D · Why it works and complexityO(n + k) timeO(n + k) spaceOpen the invariant and proofExplore the proofHide the proof
Prefix sums turn 'how many of each key' into 'where each key's block ends', and a back-to-front placement pass preserves ties. That is the canonical stable counting sort and the literal inner loop of radix sort.
ComplexityO(n + k) time, O(n + k) space — Counting and the prefix-sum pass are O(n + k); placing each of the n items is O(n). The output array and count/position arrays are O(n + k) space.
E · Edge cases and pitfalls5single key for all — One block of size n; reverse placement reproduces input order — stable.Explore edge casesHide edge cases
Edge cases handled
single key for allOne block of size n; reverse placement reproduces input order — stable.keys 0 and 99 onlyTwo blocks at the ends of the output; middle keys contribute nothing.
Pitfalls
- Computing the dash AFTER reordering — the index no longer reflects the original position.
- Scanning forward with end-positions (or backward with start-positions) — breaks stability.
- Using exclusive vs inclusive prefix sums inconsistently and overwriting slots.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic String fullCountingSort(int[] keys, String[] vals) { int n = keys.length; List<List<String>> buckets = new ArrayList<>(); for (int v = 0; v < 100; v++) { buckets.add(new ArrayList<>());Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Unstable sort on equal keys
The problem demands input order among equal keys. A generic sort that is not stable shuffles ties and fails.
Explore the code comparisonHide the code comparison
Unstable sort on equal keys
The problem demands input order among equal keys. A generic sort that is not stable shuffles ties and fails.
Don't write this
items.sort(key=lambda p: p[0]) # may reorder equal keys if not guaranteed stable in your implWrite this instead
buckets = [[] for _ in range(100)]
for i, (k, s) in enumerate(items):
buckets[k].append('-' if i < n // 2 else s)2Wrong half / off-by-one on the dash
The first half is 0-based indices 0..n/2-1. Using i <= n/2 or 1-based indexing dashes one item too many or too few.
Explore the code comparisonHide the code comparison
Wrong half / off-by-one on the dash
The first half is 0-based indices 0..n/2-1. Using i <= n/2 or 1-based indexing dashes one item too many or too few.
Don't write this
String s = (i <= n / 2) ? "-" : vals[i];Write this instead
String s = (i < n / 2) ? "-" : vals[i];Revise
Carry the pattern into the interview
Pattern to remember
Stable counting sort by integer key carrying a string payload
Code pattern to memorize
for i, (k, s) in enumerate(items):
s = '-' if i < n // 2 else s
bucket[k].append(s) # append preserves input order → stable
output = []
for k in 0..99: output += bucket[k]
print(' '.join(output))Rules to remember
- 1Replace the string with
'-'when the 0-based indexi < n/2(use integer divisionn // 2). - 2Sort by key only; the string is satellite data carried along, never compared.
- 3Stability is mandatory: equal keys must stay in input order. Appending to per-key buckets while scanning left-to-right guarantees it.
- 4Walk buckets in ascending key order to emit the final sequence — that produces the sorted-by-key output.
- 5Output is the strings joined by spaces, not the keys.
Use this pattern when
- Sorting records by a small bounded integer key while preserving ties' order
- You need a stable linear-time sort
- Building one digit-pass of radix sort
- Keys are integers in a known small range and payloads must travel with them
Choose another pattern when
- Key range is huge → bucket array is too big; use a stable comparison sort
- No stability requirement and keys are bare integers → plain counting sort suffices
- Keys are non-integer or unbounded → counting sort does not apply
Interview takeaway
Counting sort becomes a record sort the moment you carry payloads in per-key buckets in input order — and that stability is precisely what lets radix sort chain multiple passes correctly.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternStable counting sort with satellite data. Bucket records by a small integer key in input order (or place via prefix sums scanning back-to-front). Linear time when k is small; stability is mandatory and is exactly what radix sort chains across digit passes.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Counting Sort 2The same stable counting sort but on bare integers without payloads.
- Radix Sort (multi-digit)Coming soonChain stable counting-sort passes from least to most significant digit.
- Sort Characters By FrequencyComing soonBucket by count, a sibling bucketing technique.