skip to content

sort.Interface and Custom Orders

Implementing Len, Less and Swap, or handing sort.Slice a closure, and knowing that sort.Sort is not stable while sort.Stable and SliceStable are.

part ofGo (Golang)overview, primer and where to startread it →
on this pageshow

questions

5

Which three methods does Go's sort.Interface require, and what does each one do?

level: juniorimportance: must knowfreq 82%

answer

  1. three methods, no element types
  2. count, compare, exchange
  3. the sort only ever sees indices
  4. Len, Less, Swap
  5. Less means must sort before

basics

~20 s

Go's sort.Interface requires three methods: Len() int returns the number of elements, Less(i, j int) bool reports whether the element at index i must sort before the one at index j, and Swap(i, j int) exchanges those two elements.

solid answer

~40 s

`sort.Interface` is three methods: `Len() int`, `Less(i, j int) bool` and `Swap(i, j int)`. The sort package never sees your elements — it works purely in indices, asks `Len` once for the count, then calls `Less` to ask about ordering and `Swap` to move elements, so the sorting happens in place inside your own container. The usual idiom is a named slice type, `type byScore []Row`, with the three methods on it, then `sort.Sort(byScore(rows))`. A value receiver is fine for a slice type, because the copied slice header still points at the same backing array, so `Swap` mutates the caller's data. `sort.IntSlice`, `sort.StringSlice` and `sort.Float64Slice` are ready-made implementations for the plain cases.

code

go · 20 lines
go
type Row struct {
	Region string
	Score  int
}

type byScore []Row

func (r byScore) Len() int {
	return len(r)
}

func (r byScore) Less(i, j int) bool {
	return r[i].Score < r[j].Score
}

func (r byScore) Swap(i, j int) {
	r[i], r[j] = r[j], r[i]
}

// sort.Sort(byScore(rows)) reorders rows in place.

go deeper

for a junior

Be ready to write the three methods from memory on a named slice type and then call sort.Sort on it. Remember that Less means must sort before, and that indexing happens inside your methods.

for a middle

Explain why the interface is index-based rather than value-based, why a value receiver on a slice type still mutates the caller's data, and which sort functions accept the interface.

for a senior

Show that you know what the Less contract actually demands and what it costs: strict ordering, transitivity, no dependence on state that moves during the sort, and one non-inlinable interface call per comparison on hot batch paths.

for a principal

Frame the choice of a named ordering type as an API decision: an exported type such as byScore fixes an ordering other packages will depend on, so decide whether the ordering belongs in your package's surface or stays an unexported implementation detail.

## The interface Go's `sort` package sorts anything that can answer three questions about itself: ```go type Interface interface { Len() int Less(i, j int) bool Swap(i, j int) } ``` - **`Len() int`** — how many elements there are. The sort calls this exactly once, at the start, to learn `n`; it then works only with indices in `[0, n)`. - **`Less(i, j int) bool`** — reports whether the element at index `i` **must sort before** the element at index `j`. Note the wording: it is a *strict* "before", not "before or equal". If `Less(i, j)` and `Less(j, i)` are both false, the sort treats the two elements as equal. - **`Swap(i, j int)`** — exchanges the elements at the two indices, in place. ## Why indices and not values This interface predates generics, and its shape is the reason `sort` works on any container at all, not just slices: a linked structure, a pair of parallel slices, a struct wrapping a slice plus a cached key — anything that can count, compare by position and swap by position. The sort algorithm never touches an element value and never allocates a copy of your data. That is also why `Swap` exists: the package cannot move elements it cannot see, so your type has to move them for it. ## The idiom The conventional implementation is a **named slice type** carrying the three methods: ```go type Row struct { Region string Score int } type byScore []Row func (r byScore) Len() int { return len(r) } func (r byScore) Less(i, j int) bool { return r[i].Score < r[j].Score } func (r byScore) Swap(i, j int) { r[i], r[j] = r[j], r[i] } ``` and then `sort.Sort(byScore(rows))`. The conversion `byScore(rows)` is free — it does not copy the elements, only reinterprets the slice's type. A **value receiver** is correct here and often surprises newcomers. `Swap` mutates the caller's data even though `r` is a copy, because copying a slice copies its three-word header (pointer, length, capacity) and the pointer still aims at the same backing array. If instead you implement the interface on a **struct** that holds the slice, a value receiver is still fine for the same reason; you only need a pointer receiver if a method must change the length or replace the slice itself. ## Who consumes the interface Once a type satisfies `sort.Interface` it can be handed to several functions: - `sort.Sort(data)` — orders it, with no guarantee about the relative order of elements your `Less` treats as equal. - `sort.Stable(data)` — orders it while keeping equal elements in their original relative order. - `sort.IsSorted(data)` — reports whether it is already ordered according to your `Less`. - `sort.Reverse(data)` — returns another `sort.Interface` with the ordering flipped. The package also ships adapters for the trivial element types: `sort.IntSlice`, `sort.StringSlice` and `sort.Float64Slice` are named slice types that already implement the three methods, so `sort.Sort(sort.IntSlice(nums))` needs no code of your own. ## The contract your Less must honour `Less` is expected to describe a consistent ordering. The package documents two transitivity requirements: if `Less(i, j)` and `Less(j, k)` are both true then `Less(i, k)` must be true, and if `Less(i, j)` and `Less(j, k)` are both false then `Less(i, k)` must be false too. A common way to break the second rule is writing `<=` instead of `<`, which claims that two equal elements each sort before the other. It also means `Less` must not consult anything that changes during the sort — reading a package-level variable that another goroutine mutates, or comparing against an element captured by value before elements started moving, produces an ordering the sort cannot make sense of. ## What it costs Sorting through this interface is allocation-free and in place, but every comparison and every move is an **interface method call**, which the compiler generally cannot inline. For a hot path over a large batch that is the main cost of the approach; for the overwhelming majority of code it is irrelevant and the readability of a named type with a named ordering wins.

  • Why does the interface pass indices rather than the two elements being compared?
    Because the sort package is not allowed to know your element type. Working in indices lets it sort any container that can count, compare by position and swap by position — a slice, a struct wrapping several parallel slices, anything — without copying elements or allocating. It is also why the type must supply `Swap`: the algorithm cannot move data it cannot see.
  • Is a value receiver enough for Swap on a named slice type?
    Yes. Copying a slice copies its header — pointer, length, capacity — and the pointer still refers to the same backing array, so `r[i], r[j] = r[j], r[i]` inside a value-receiver method mutates the caller's elements. You would need a pointer receiver only if a method had to change the slice's length or replace the slice value itself.
  • What does sort.IsSorted do with your Less?
    `sort.IsSorted(data)` walks the data and reports whether every adjacent pair is in order according to your own `Less`, without moving anything. It is a cheap assertion in a test or a debug check, and it is also a useful smoke test of the `Less` itself: if it reports false immediately after a successful sort, the ordering method is inconsistent.

saying these in an interview costs you the question

  • Says Less receives the two element values
  • Writes Less as less-than-or-equal instead of strict less-than
  • Thinks Swap must be on a pointer receiver for a slice type
  • Believes the sort package copies the data before ordering it
  • Cannot name Len, and calls the method Size or Count
open as a page

How do you write a multi-key Less for sort.Interface so ties fall back to a second field?

level: middleimportance: must knowfreq 64%

basics

~20 s

Compare one key at a time: if the primary fields differ, return that comparison; otherwise fall through to the next field, and end with a unique tie-breaker. Never join key comparisons with || or &&, which produces an inconsistent ordering.

open as a page

What does sort.Slice give you over implementing sort.Interface, and what does it cost?

level: middleimportance: must knowfreq 68%

basics

~20 s

sort.Slice takes the slice plus a less closure, so you skip declaring a named type with Len, Less and Swap. The cost: it accepts any, so a non-slice argument panics at run time instead of failing to compile, and it is not stable — sort.SliceStable is.

open as a page

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%

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.

open as a page

A pull request's sort.Interface Less returns true for rows equal on every key — what breaks?

level: seniorimportance: should knowfreq 44%

basics

~20 s

The ordering stops being consistent: no pair is ever recognised as equal, so the resulting order is arbitrary, sort.Stable can no longer preserve input order, and sort.IsSorted may report false on the sort's own output. Nothing is lost — the sort only swaps.

open as a page