Why did a radix sort place -5 after 1000000 in its output once the keys included signed values?
answer
- look at the whole output, not one value
- which block landed in the wrong place?
- buckets concatenate in ascending index order
- what does the top bit do to the last pass?
- shift the range, or rotate the last pass
basics
~20 sThe passes read every key as an unsigned pattern, and negatives carry the top bit set, so they land in the final pass's highest buckets and come out last. Fix it by biasing the keys, or by rotating the top digit's bucket order.
solid answer
~50 sRadix sort orders by digit value, and on the most significant pass it emits bucket 0 first through bucket `k-1` last. Negative keys have the top bit set, so as raw patterns they are the largest values in that domain and land in the upper buckets — the output ends up as "non-negatives ascending, then negatives ascending", which is exactly the reported symptom. The internal order among negatives is actually correct; only their position as a block is wrong. Two fixes work: add a bias of `2^(w-1)` to every key before sorting and subtract it after, which maps the signed range order-preservingly onto the non-negative range (best folded into digit extraction so it costs no extra pass); or leave the data alone and, in the **final** pass only, emit the upper half of the buckets before the lower half. Floating-point keys need a different transform and should not be assumed to work.
code
pseudocode · 11 lines// keys are signed and w bits wide; base k = 2^b, d = ceil(w / b) passes
BIAS = 2^(w-1) // maps the signed range onto 0 .. 2^w - 1, order preserved
for pos in 0..d-1:
for b in 0..k-1:
bucket[b] = empty
for i in 0..length(a)-1:
u = a[i] + BIAS // computed in the unsigned domain
digit = (u / k^pos) mod k
add a[i] to the end of bucket[digit] // store the original key
... concatenate bucket[0..k-1] back into a, in index order ...
// records are never rewritten, so the array survives an aborted sortgo deeper
Know that radix passes read keys as raw patterns and concatenate buckets from lowest index to highest, so anything that makes a larger pattern mean a smaller value breaks the output order.
Explain why the misplacement is a contiguous block rather than a scramble, and describe the bias transform that maps the signed range order-preservingly onto the non-negative range.
Diagnose from the output shape, propose both fixes with their tradeoffs, and name the test fixture that straddles zero. Flag that fractional keys need a different transform rather than the same one.
Treat this as evidence about test-data policy: a sort correct only on non-negative inputs shipped because fixtures never crossed zero. Decide whether owning a hand-rolled digit sort is worth that recurring correctness surface.
## Reading the symptom precisely The report is "`-5` sorted after `1000000`". Before theorising, look at the whole output: the giveaway is that it is not randomly wrong. It reads as every non-negative value in correct ascending order, followed by every negative value — usually also in correct ascending order among themselves. One contiguous block is in the wrong place. That signature immediately rules out a stability bug (which scrambles low digits) and points at how the most significant digit is being interpreted. ## The mechanism Radix passes do not know what a key *means*. They extract a digit and place the record in the bucket with that digit's index, and at the end of a pass buckets are concatenated in index order: bucket 0, then 1, and so on up to `k-1`. Correctness therefore rests on an assumption: **larger digit value must mean later in the desired order**. For keys stored in a signed `w`-bit representation, negative values carry a set top bit. That bit sits in the digit examined by the last pass, so every negative key's final-pass digit falls in the upper half of the digit range while every non-negative key's falls in the lower half. Concatenating buckets in ascending index order then puts the entire negative block last. The assumption "larger digit value means later" is violated for exactly one bit of the key, and it happens to be the bit the last pass depends on most. Note what is **not** broken: within the negatives, ordering is right, because for two negative values the remaining digits still increase in the same direction as the values. The bug is a block placement, not a scramble — which is why a test suite with only non-negative data passes cleanly and the defect surfaces the first time real data contains a refund, an adjustment, or an offset before an epoch. ## Fix one: bias the keys Add `2^(w-1)` to every key and interpret the result in the unsigned domain. This maps the smallest representable negative value to 0 and the largest positive to the top of the unsigned range, preserving order exactly. Sort as usual on the biased values, then subtract the bias when writing results back. Done naively this is two extra full passes over the data. Done well it costs nothing measurable: fold the bias into digit extraction so the top-digit computation adds it on the fly, leaving the stored records untouched. That keeps the sort non-destructive, which matters when the records are shared or the sort may be aborted. ## Fix two: rotate the final pass's bucket order Leave the keys alone and change the concatenation order of the **last** pass only: emit buckets `k/2 .. k-1` first, then buckets `0 .. k/2-1`. This puts the negative block ahead of the non-negatives while leaving each block internally ordered, which is precisely the correction needed. It is the cheapest fix — one changed loop bound in one pass — and the most dangerous to maintain, because it is a special case that lives in one place and looks like a bug to the next reader. If you take it, the comment above it must say why. Applying the rotation to *every* pass instead of the last is a classic follow-on defect: the lower digits have no sign meaning, so rotating them corrupts the order outright. ## The adjacent trap: fractional keys Do not assume the same fix generalises. Standard floating-point layouts also put a sign bit on top, but negative values additionally run in **decreasing** magnitude order as raw patterns, so a bias alone leaves the negatives internally reversed. The known transform inverts all bits of negative keys and only the sign bit of non-negative ones, producing an order-preserving unsigned image. The interview-safe answer is to say the signed-integer fix does not transfer unchanged, and that fractional keys need their own order-preserving transform plus tests around zero and the special values. ## Testing the fix The reason this defect ships is that the test data has no negatives. Pin it with a fixture that straddles zero deliberately: the most negative representable value, a small negative, negative and positive zero-adjacent values, a small positive, and the largest representative positive — plus duplicates spanning the sign boundary to confirm stability survives the transform. Compare against a reference sort on the same input rather than eyeballing the output. If the bias is folded into digit extraction, add a case asserting the input array is unmodified when the sort is abandoned midway. ## Why interviewers like this one It separates candidates who have run a digit sort on real data from those who have only read the pseudocode. The reasoning chain is short but requires holding two ideas at once: buckets are concatenated in ascending index order, and the raw pattern order of signed keys does not match their numeric order. Getting there, naming both fixes, and flagging that fractional keys need a different treatment is a complete senior answer.
- Between biasing the keys and rotating the last pass's bucket order, which would you ship?Biasing, folded into digit extraction. It costs no extra pass, leaves the stored records untouched, and keeps every pass uniform, so the next reader has no special case to rediscover. Bucket rotation is a single changed loop bound and slightly cheaper, but it is a silent special case living in one pass, and applying it to the wrong passes corrupts the output. If you take it, the comment explaining why is mandatory.
- Does the same fix let you radix sort fractional keys?No. Standard floating-point layouts do put a sign bit on top, but negative values run in decreasing magnitude order as raw patterns, so a bias alone leaves the negatives internally reversed. The usable transform inverts all bits of negative keys and only the sign bit of the rest, producing an order-preserving unsigned image. Treat it as a separate design decision with its own tests around zero and the special values.
- How would you have caught this before it reached production?With a fixture that deliberately straddles zero: the most negative representable value, a small negative, small positives, the largest representative positive, and duplicates spanning the sign boundary to confirm stability survives the transform. Compare against a reference sort on the same input instead of eyeballing. Test data drawn only from identifiers or counts is exactly why the defect ships.
saying these in an interview costs you the question
- Blames an unstable pass rather than bucket ordering
- Rotates bucket order in every pass, not just the last
- Claims the negatives are internally scrambled too
- Assumes the signed fix transfers to fractional keys unchanged
- Suggests sorting magnitudes without partitioning by sign first