skip to content

Slices, Maps and Ordering

Working with Go's two built-in containers through the generic slices, maps and cmp helpers: copying them, comparing them, ordering them, and living with a map that hands you no order at all.

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

explore

questions

27

What does Go's cmp.Compare return, and which types can you call it on?

level: juniorimportance: must knowfreq 42%

answer

  1. a three-way answer, not a bool
  2. minus one, zero, plus one
  3. the constraint lists ints, floats, string
  4. tilde means your named types count too
  5. structs and time.Time are outside it

basics

~10 s

cmp.Compare returns -1 if the first argument is less, 0 if they are equal, and +1 if it is greater. It accepts any cmp.Ordered type: integers, floats, strings, and named types built on them.

solid answer

~40 s

The signature is `func Compare[T cmp.Ordered](x, y T) int`, and it returns exactly one of three values: `-1`, `0`, `+1`. It is a generic function, so the comparison is the built-in `<` operator applied to the concrete type — no method, no interface, no boxing. `cmp.Ordered` is a constraint whose type set is every integer kind, `float32`, `float64` and `string`, each written with a `~` so a defined type such as `type Score float64` satisfies it too. `cmp.Less` is the same idea returning a `bool`. You reach for `cmp.Compare` when you need a single integer that expresses less/equal/greater — for example to chain tie-breaks through `cmp.Or`. Types whose order lives in a method, such as `time.Time`, are not in the set; those carry their own `Compare` method instead.

code

go · 4 lines
go
fmt.Println(cmp.Compare(2, 10))    // -1
fmt.Println(cmp.Compare("b", "a")) // 1
fmt.Println(cmp.Compare(3.5, 3.5)) // 0
fmt.Println(cmp.Less(2, 10))       // true

go deeper

for a junior

Be ready to state the three possible results and name the kinds of types cmp.Ordered covers: integers, floats and string. Knowing cmp.Less is the bool-shaped twin is enough at this level.

for a middle

Explain why the constraint is written with the tilde approximation, so your own named types work, and why a struct such as time.Time is excluded and carries its own Compare method instead.

for a senior

Show you know when the three-way int actually earns its keep over a plain comparison operator: when the result has to be combined across keys, and when floating-point edge cases must be defined rather than left false.

for a principal

Own the API consequence: constraining to cmp.Ordered gives the cheapest call site but closes the door on element types whose order lives in a method, and that door cannot be reopened without changing an exported signature.

## What the function is `cmp` is a very small standard-library package. It contains exactly four exported names: the constraint `cmp.Ordered`, and the functions `cmp.Compare`, `cmp.Less` and `cmp.Or`. The first three are about ordering; the fourth is about picking a first non-zero value. `cmp.Compare` has this shape: ```go func Compare[T cmp.Ordered](x, y T) int ``` It returns: - `-1` when `x` is less than `y` - `0` when `x` equals `y` - `+1` when `x` is greater than `y` Those are the only three values it can produce. It is not a C-style `strcmp` that returns "some negative number" such as the arithmetic difference — the documented result is exactly `-1`, `0` or `+1`, so it is safe to compare the result against those constants directly, though idiomatic Go tests `< 0`, `== 0`, `> 0`. ## What cmp.Ordered admits `cmp.Ordered` is a **constraint**, not an ordinary interface. Its type set is: - every signed integer kind: `int`, `int8`, `int16`, `int32`, `int64` - every unsigned integer kind: `uint`, `uint8`, `uint16`, `uint32`, `uint64`, `uintptr` - both floating-point kinds: `float32`, `float64` - `string` Each element is written with the `~` approximation, so the set contains not only those predeclared types but every defined type whose *underlying* type is one of them. That means your own `type Score float64` or `type UserID int64` can be passed to `cmp.Compare` with no extra work. What is **not** in the set is just as important: `bool`, complex numbers, pointers, channels, arrays, structs, slices and maps. `time.Time` is a struct, so `cmp.Compare(t1, t2)` does not compile — `time.Time` supplies its own `Compare` method (and `Before`/`After`) for that job. A type that wants to be ordered by a helper constrained to `cmp.Ordered` has to *be* a number or a string underneath; it cannot opt in by declaring a method. Because `cmp.Ordered` contains type-set elements rather than methods, it can only be used as a type constraint. Writing `var x cmp.Ordered` is a compile error — there is no such interface value at run time. ## Why a constraint and not an interface The reason the ordering helpers in the standard library are generic over `cmp.Ordered` rather than taking a `Comparable` interface is that you cannot add a method to `int`. Methods can only be declared on types defined in your own package, so an interface-based ordering API would force every caller to wrap `int`, `string` and `float64` in a named type before it could sort them. A constraint sidesteps that entirely: the compiler instantiates the function for the concrete type and emits the machine comparison directly, with no interface value to allocate and no dynamic dispatch on the hot path. The price is that the set is closed. If your element ordering needs more than `<` — a struct sorted by two fields, a case-insensitive string order, a locale-aware collation — a constraint cannot express it, and the API has to accept a comparison function instead. ## cmp.Compare versus cmp.Less versus the < operator `cmp.Less[T cmp.Ordered](x, y T) bool` answers the same question as `x < y` for a bool-shaped caller. `cmp.Compare` answers it in one integer, which is what you want when the result must be *combined* — for example folding several keys into a single decision with `cmp.Or`, where a `0` from the first key means "undecided, look at the next key". For integers and strings, `cmp.Compare(x, y)` and the raw operators agree completely. For floating-point values they do not: `cmp.Compare` defines an order for NaN and for negative zero, while the bare `<` and `==` operators report false for every comparison involving a NaN. That difference is the whole reason the function exists rather than each caller writing three lines of `if`. ## A small example ```go cmp.Compare(2, 10) // -1 cmp.Compare("b", "a") // +1 cmp.Compare(3.5, 3.5) // 0 cmp.Less(2, 10) // true ``` Type inference picks `T` from the arguments, so you almost never write the type argument explicitly. Both arguments must have the same type: `cmp.Compare(1, "a")` does not compile, and neither does `cmp.Compare(int32(1), int64(1))` without a conversion.

  • What does cmp.Less give you that cmp.Compare does not?
    `cmp.Less` returns a `bool`, which reads better inside an `if`. `cmp.Compare` returns an `int` so the answer can be combined — a `0` means "these keys are tied, consult the next one", which a `bool` cannot express. Both apply the same floating-point rules, so they never disagree.
  • Why can't you call cmp.Compare on two time.Time values?
    `cmp.Ordered`'s type set is the integer kinds, `float32`, `float64` and `string`. `time.Time` is a struct, so it is not in the set and the call does not compile. Its ordering lives in methods instead: `time.Time.Compare` returns the same -1/0/+1, and `Before`/`After` return bools.
  • Does cmp.Ordered accept a defined type such as type Score float64?
    Yes. Every element of the type set is written with the `~` approximation, which matches any type whose underlying type is that basic kind. So `Score`, `type UserID int64` and `type Name string` all satisfy `cmp.Ordered` without any declaration on your side.
  • Can you declare a variable of type cmp.Ordered?
    No. `cmp.Ordered` is an interface that contains type-set elements rather than methods, and such interfaces may only be used as constraints. `var x cmp.Ordered` is a compile error; the type only exists to bound a type parameter, never to hold a value at run time.

saying these in an interview costs you the question

  • Says cmp.Compare returns a bool
  • Thinks it returns the arithmetic difference of the two values
  • Believes cmp.Ordered includes structs such as time.Time
  • Thinks the element type must declare a Compare method
  • Assumes it works on numbers only, not on strings
open as a page

In Go's container/heap, which five methods must your type implement?

level: juniorimportance: must knowfreq 34%

basics

~20 s

container/heap.Interface embeds sort.Interface, so your type needs Len, Less and Swap, plus Push(x any) to append one element and Pop() any to remove and return the last one. Five methods, and you own the storage.

open as a page

Why does ranging over a Go map yield a different order each run, and how do you produce stable output?

level: juniorimportance: must knowfreq 78%

basics

~10 s

Go deliberately randomises map iteration, so no order is guaranteed or repeatable. For stable output, copy the keys into a slice, sort that slice, then read the map in that key order.

open as a page

What does slices.Clone(s) copy that the plain assignment b := s does not?

level: juniorimportance: must knowfreq 68%

basics

~10 s

slices.Clone allocates a new backing array and copies the elements into it, so the two slices no longer share storage. Plain assignment copies only the slice header, leaving both names pointing at one array.

open as a page

What does slices.Sort do to a slice, and which element types can it sort?

level: juniorimportance: must knowfreq 62%

basics

~20 s

slices.Sort reorders a slice in place into ascending order and returns nothing. It is generic over element types that support the less-than operator - integers, floats and strings - so no comparison callback or interface implementation is needed.

open as a page

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

level: juniorimportance: must knowfreq 82%

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.

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

How does Go's cmp.Compare order a NaN float64, and what does it return for two NaNs?

level: middleimportance: should knowfreq 28%

basics

~10 s

cmp.Compare treats a NaN as smaller than every non-NaN value, so NaNs come first, and reports two NaNs as equal by returning 0. Negative and positive zero also compare equal, giving a total order.

open as a page

What does Go's cmp.Or return, and does it stop evaluating arguments at the first non-zero one?

level: middleimportance: should knowfreq 32%

basics

~10 s

cmp.Or returns the first argument that is not the zero value of its type, or the zero value if none is. It is an ordinary call, so every argument is evaluated first: no short-circuiting.

open as a page

Why does calling the Pop method on a container/heap queue directly return the wrong item?

level: middleimportance: should knowfreq 42%

basics

~20 s

Your Pop method only detaches the last slice element and knows nothing about ordering. heap.Pop swaps the root to the end, sifts the heap back into shape, and only then calls your method, so only heap.Pop yields the smallest item.

open as a page

Why must Push and Pop on a container/heap queue use a pointer receiver?

level: middleimportance: should knowfreq 30%

basics

~20 s

Push and Pop change the queue's length. A value receiver would append to a copy of the slice header, so both assign back through the pointer — which means only the pointer type satisfies heap.Interface.

open as a page

You sort a slice built from a map[string]int by count, yet equal counts come out in a different order each run. Why?

level: middleimportance: should knowfreq 46%

basics

~20 s

The slice was filled by ranging the map, so its starting order is random, and sorting only by count leaves equal counts in that random order. Fix it by making the comparison a total order: break ties on the key.

open as a page

Why must the result of slices.Delete(s, i, j) be assigned back, and what happens to the tail it vacates?

level: middleimportance: should knowfreq 44%

basics

~20 s

slices.Delete shifts the later elements left inside the same backing array and returns a shorter header, so a variable you never reassign still shows the old length. Since Go 1.22 it also zeroes the slots it vacates.

open as a page

Why does slices.Equal treat a nil []string and an empty []string as equal when reflect.DeepEqual does not?

level: middleimportance: should knowfreq 52%

basics

~20 s

slices.Equal compares length and then elements, and both slices have length zero with no elements. reflect.DeepEqual adds a rule that two slices must be both nil or both non-nil, so it reports them as different.

open as a page

When slices.BinarySearch reports the target is absent, what does its returned index mean?

level: middleimportance: should knowfreq 45%

basics

~20 s

It is the insertion point: the position the target would occupy if it were placed in the sorted slice. It ranges from 0 to len(s) inclusive, so it can be one past the last element and must be range-checked before indexing.

open as a page

Why does slices.SortFunc take a comparison returning an int rather than a bool?

level: middleimportance: should knowfreq 55%

basics

~20 s

slices.SortFunc's callback returns a negative, zero or positive int meaning a sorts before, equal to, or after b. The three-way result reports equality, which a boolean less-than cannot, so one function can also drive stable sorting and binary search.

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 float64 comparator returns 0 whenever either score is NaN; ordering the same leaderboard twice yields different results. Why?

level: seniorimportance: should knowfreq 25%

basics

~20 s

Returning 0 for NaN pairs claims a NaN equals every score, so two differing scores both equal it. Equality is no longer transitive, the comparison is not a valid ordering, and the result depends on input order.

open as a page

A Go code generator walking a map of type descriptors emits a different file each run. How do you make its output byte-identical?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Every place the emitter ranges a map is a source of randomised order. Sort the keys at each emission site, or keep the schema's order in slices and use maps only for lookup, then assert byte-identical output by generating twice in one test.

open as a page

Why can slices.BinarySearchFunc return a confident wrong hit, and how do you catch it?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Binary search assumes the slice is sorted by the comparison it is given. Data ordered by a different key makes it halve the wrong way and return a plausible index with no error. Assert sortedness, and test against a linear scan.

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

Why does fmt.Println print a Go map's keys in sorted order when a range loop over the same map does not?

level: middleimportance: nice to knowfreq 32%

basics

~20 s

The fmt package sorts a map's keys itself before printing, so its output is stable. The map is still unordered; ranging over it directly gives a randomised order. encoding/json sorts map keys when marshalling for the same reason.

open as a page

A daemon calls heap.Fix after re-prioritising a queued job, yet the wrong jobs fire. What went wrong?

level: seniorimportance: nice to knowfreq 26%

basics

~20 s

container/heap tracks no positions, so heap.Fix trusts the index you pass it. If Swap does not write each element's new position into its index field, that field goes stale after the first sift and Fix repairs the wrong slot.

open as a page

A reconcile loop clones observed state with maps.Clone, mutates the clone, and never converges — how do you find the cause?

level: seniorimportance: nice to knowfreq 32%

basics

~20 s

maps.Clone and slices.Clone copy one level only, so nested slices and maps in the copy still share storage with the observed snapshot. Mutating the copy edits the snapshot too, and the desired-versus-observed diff comes out wrong on every tick.

open as a page

How does sort.Search work over an index space, and what must its predicate guarantee?

level: seniorimportance: nice to knowfreq 26%

basics

~20 s

sort.Search(n, f) returns the smallest index i in the range 0 to n-1 where f(i) is true, and returns n when none is. The predicate must be false for a prefix of that range and true for the rest.

open as a page

cmp.Ordered or a comparator function: which shape do you commit to for an ordering API other teams import?

level: principalimportance: nice to knowfreq 20%

basics

~10 s

Make the comparator-taking function the primitive and add a cmp.Ordered-constrained wrapper on top. Adding an exported function later is compatible; changing one's parameters is not, so commit to the shape that excludes nobody.

open as a page