skip to content

What does sort.Reverse do to a sort.Interface, and why is it not the same as reversing the slice?

level: middleimportance: should knowfreq 52%

answer

  1. a wrapper, not an operation
  2. one method overridden by embedding
  3. the arguments are swapped, not the elements
  4. it flips the tie-breaker too
  5. still needs sort.Sort to do anything

basics

~10 s

sort.Reverse returns a new sort.Interface that wraps yours and calls your Less with its two arguments swapped. It only flips the ordering — it moves nothing until you pass it to sort.Sort or sort.Stable.

solid answer

~40 s

`sort.Reverse(data)` returns another `sort.Interface` that embeds `data` and overrides one method: its `Less(i, j)` calls `data.Less(j, i)`. `Len` and `Swap` pass straight through, so nothing is copied and nothing moves — you still have to hand the wrapper to `sort.Sort(sort.Reverse(data))` to get a descending order. Two consequences matter in practice. First, it flips the **whole** ordering, so a multi-key `Less` comes out with every key reversed, including the tie-breaker; a report that wants score descending but name ascending has to express that inside `Less`, not by wrapping. Second, stability survives the wrapping: rows equal under the original `Less` are still equal under the flipped one, so `sort.Stable(sort.Reverse(data))` keeps their input order.

code

go · 9 lines
go
// Equivalent to what sort.Reverse hands back:
type flipped struct{ sort.Interface }

func (f flipped) Less(i, j int) bool {
	return f.Interface.Less(j, i)
}

sort.Sort(sort.Reverse(byScore(rows)))
sort.Sort(sort.Reverse(sort.IntSlice(nums)))

go deeper

for a junior

Know that sort.Reverse returns something you still have to pass to sort.Sort, and that it produces a descending order rather than physically mirroring the slice.

for a middle

Explain the implementation — an embedded sort.Interface with Less called on swapped arguments — and why Len and Swap pass through unchanged.

for a senior

Show where wrapping is the wrong tool: mixed key directions must live inside the comparison cascade, and maintaining a separate descending ordering type is how ascending and descending versions drift apart.

for a principal

Decide what a package exposes. Exporting one ordering type that callers may wrap keeps a single definition of the ranking, whereas exporting an ascending and a descending variant doubles the surface you must keep consistent forever.

## What the wrapper is `sort.Reverse(data Interface) Interface` does not sort, reverse, or copy anything. It returns a value that **embeds** the `sort.Interface` you gave it and replaces a single method. Written out, it is equivalent to: ```go type flipped struct{ sort.Interface } func (f flipped) Less(i, j int) bool { return f.Interface.Less(j, i) } ``` Embedding promotes `Len` and `Swap` unchanged, so the wrapper counts and moves exactly the same elements as the original; only the question "does `i` come before `j`?" is asked backwards. This is a small, instructive piece of the standard library: it shows interface embedding used to override one method of an existing implementation without knowing the concrete type behind it. Because it returns an interface value and does no work, the wrapper is inert on its own: ```go sort.Reverse(byScore(rows)) // does nothing at all sort.Sort(sort.Reverse(byScore(rows))) // sorts descending sort.Stable(sort.Reverse(byScore(rows))) // same, ties keep input order ``` A reviewer sees the first line about once a year, and it is always a bug. ## Flipping is not reversing "Reverse the ordering" and "reverse the slice" are different operations, and they only coincide when the data is already sorted ascending with no ties. Wrapping and sorting asks the algorithm to build a descending order from wherever the elements currently are; physically reversing a slice mirrors its current arrangement, whatever that is. If the input is unsorted, mirroring it gives you nothing useful. ## It flips every key This is the trap worth remembering. A multi-key `Less` might rank by score ascending and then by name ascending. Wrap it and you get score **descending** and name **descending** — the tie-breaker is reversed too, because the wrapper knows nothing about your keys and simply asks the whole comparison backwards. So a common ranked-report requirement — highest score first, alphabetical within a tie — cannot be expressed by wrapping at all. It has to be written into the comparison cascade directly, with `>` on the score and `<` on the name: ```go func (r byRank) Less(i, j int) bool { a, b := r[i], r[j] if a.Score != b.Score { return a.Score > b.Score // descending } return a.Name < b.Name // ascending within a tie } ``` The rule of thumb: `sort.Reverse` is right when the ordering is one-dimensional or when *every* key genuinely reverses together; anything mixed belongs inside `Less`. ## Stability under the wrapper A reasonable worry is whether wrapping destroys `sort.Stable`'s guarantee. It does not. Two elements are equal when the comparison is false in both directions, and swapping the arguments of a symmetric-false pair leaves it symmetric-false: equals under the original `Less` are exactly the equals under the flipped one. So `sort.Stable(sort.Reverse(data))` still preserves the input order of tied elements. What changes is only which non-equal pairs are ordered which way. ## Where it shines The wrapper's real value is that it composes with orderings you did not write. Given a package that exports a `sort.Interface` for its own type, or the standard adapters `sort.IntSlice`, `sort.StringSlice` and `sort.Float64Slice`, you get the descending sort for free: ```go sort.Sort(sort.Reverse(sort.IntSlice(nums))) ``` No second type, no duplicated comparison logic, and no risk of the ascending and descending versions drifting apart — which is exactly what happens when a codebase grows a `byScore` and a `byScoreDesc` side by side and only one of them gets the new tie-breaker. ## Checking your work `sort.IsSorted(sort.Reverse(data))` reports whether the data is in the reversed order, which is a tidy assertion in a test: build the wrapper once, sort through it, then ask the same wrapper whether the result satisfies it.

  • How is sort.Reverse implemented?
    As a struct that embeds the `sort.Interface` it was given and defines its own `Less(i, j)` returning the embedded `Less(j, i)`. Embedding promotes `Len` and `Swap` unchanged, so the wrapper sorts the same elements in place. It is a neat example of overriding exactly one method of an implementation whose concrete type you do not know.
  • You need score descending but name ascending within ties. Can sort.Reverse do that?
    No. The wrapper flips the entire comparison, so both keys reverse together and you get names in descending order too. Mixed directions have to be written into the comparison cascade itself — `>` on the score, `<` on the name — which is one of the reasons a multi-key ordering is usually spelled out by hand rather than composed.
  • Does sort.Stable still preserve input order when wrapped in sort.Reverse?
    Yes. Equality means the comparison is false in both directions, and swapping its arguments keeps that symmetric, so the wrapper and the original agree on exactly which elements are equal. `sort.Stable` therefore still keeps those elements in their input order; only the ordering of non-equal pairs changes.

saying these in an interview costs you the question

  • Thinks sort.Reverse reverses the elements in place
  • Calls sort.Reverse on its own and expects sorted data
  • Expects a wrapped multi-key ordering to keep its tie-breaker ascending
  • Says wrapping in sort.Reverse destroys stability
  • Duplicates a whole ordering type just to get descending order