Go Module 4 – Arrays, slices, maps and strings
The slices package: sort, search and compare
Sort Go slices with slices.Sort and SortFunc, order rows by several keys with cmp.Compare and cmp.Or, and search sorted data with BinarySearch.
What you will learn
- Sort slices with slices.Sort and slices.SortFunc, and predict the byte order of strings
- Write comparison functions on several keys with cmp.Compare and cmp.Or
- Search a sorted slice with slices.BinarySearch and insert at the position it returns
- Choose between slices.SortFunc, slices.SortStableFunc and the sort package
- Remove, compact and compare elements with slices.Delete, slices.Compact and slices.Equal
Before you start
What you’ll learn · Before you start
What you will learn
- Sort slices with slices.Sort and slices.SortFunc, and predict the byte order of strings
- Write comparison functions on several keys with cmp.Compare and cmp.Or
- Search a sorted slice with slices.BinarySearch and insert at the position it returns
- Choose between slices.SortFunc, slices.SortStableFunc and the sort package
- Remove, compact and compare elements with slices.Delete, slices.Compact and slices.Equal
Before you start
On this page
Six exam results ordered by marks, highest first, with tied marks in name order, take one call in Go: slices.SortFunc
with a comparison built from cmp.Compare and cmp.Or. The slices package, part of the standard library since Go
1.21, also searches sorted data, edits slices in place and compares them, with generic functions that the compiler
checks for each element type.
You will sort numbers and strings, write comparisons on several keys, find values with a binary search and insert new ones where they belong. Each output below is the one its program printed when it ran with Go 1.27.1.
slices.Sort puts numbers and strings in order, strings by their bytes
slices.Sort sorts any slice whose elements are ordered, integers, floats or strings, in place and from the smallest:
// sort_basics.go: slices.Sort on numbers and strings, and strings ordered by their bytes.
package main
import (
"fmt"
"slices"
"strings"
)
func main() {
marks := []int{67, 91, 42, 88, 58}
slices.Sort(marks) // in place, smallest first
fmt.Println(marks, slices.IsSorted(marks))
names := []string{"arjun", "Zoya", "Meera", "bala"}
slices.Sort(names) // byte by byte: 'M' (77) and 'Z' (90) come before 'a' (97)
fmt.Println(names)
slices.SortFunc(names, func(a, b string) int {
return strings.Compare(strings.ToLower(a), strings.ToLower(b))
})
fmt.Println(names)
fmt.Println(slices.Min(marks), slices.Max(marks), slices.Index(marks, 88))
} Output
[42 58 67 88 91] true [Meera Zoya arjun bala] [arjun bala Meera Zoya] 42 91 3
Recorded with Go 1.27.1 on macOS 26 arm64. To run it yourself: go run sort_basics.go
The marks came out as expected, and slices.IsSorted confirmed it. The names did not: [Meera Zoya arjun bala].
Strings compare byte by byte, and in ASCII every capital letter, 65 to 90, comes before every small letter, 97 to 122,
so both capitalised names sorted first. A comparison function: A function of two elements, a and b, that returns a negative number when a sorts first, a positive one when b does, and 0 when they tie, as slices.SortFunc expects. passed to
slices.SortFunc can compare something else instead, here the names in lower case, which gave [arjun bala Meera Zoya]. The last line shows three more functions that read a slice: slices.Min, slices.Max and slices.Index,
which returned 3, the position of 88 after the sort.
Lower-case keys once, not in every comparison
strings.ToLower inside a comparison builds two new strings on every call. For a large slice, compute each key
once, store it next to its element, and sort by the stored key.
Checkpoint
Check: Sorting numbers and strings
A comparison returns a sign, and cmp.Or chains the keys
slices.SortFunc calls its function with two elements, a and b, and reads only the sign of the result: negative
puts a first, positive puts b first, and zero means they tie. cmp.Compare(x, y) returns -1, 0 or +1, so it
supplies the sign for one key, and cmp.Or returns the first of its arguments that is not zero, which chains keys:
// leaderboard.go: marks highest first, then names A to Z where marks tie, with cmp.Compare and cmp.Or.
package main
import (
"cmp"
"fmt"
"slices"
)
type Result struct {
Name string
Marks int
}
func main() {
results := []Result{
{"Ravi", 88}, {"Asha", 91}, {"Kiran", 88},
{"Meera", 75}, {"Dev", 91}, {"Bala", 88},
}
slices.SortFunc(results, func(a, b Result) int {
return cmp.Or(
cmp.Compare(b.Marks, a.Marks), // b before a: highest marks first
cmp.Compare(a.Name, b.Name), // then names in order
)
})
for i, r := range results {
fmt.Printf("%d. %-5s %d\n", i+1, r.Name, r.Marks)
}
fmt.Println(cmp.Compare(88, 91), cmp.Compare(91, 91), cmp.Compare("Ravi", "Kiran"))
} Output
1. Asha 91 2. Dev 91 3. Bala 88 4. Kiran 88 5. Ravi 88 6. Meera 75 -1 0 1
Recorded with Go 1.27.1 on macOS 26 arm64. To run it yourself: go run leaderboard.go
Swapping the arguments, cmp.Compare(b.Marks, a.Marks), turned the order round, so the marks ran from 91 down. The
three rows with 88 tied on marks, and the names settled them. Follow the function through two of its calls:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- SortFunc asks. While it sorts, SortFunc calls the function with two rows, a and b. A negative result puts a first, a positive one puts b first, and 0 means the two tie.
- Marks first. cmp.Compare(b.Marks, a.Marks) compares Kiran's 88 with Ravi's 88. They are equal, so it returns 0, and cmp.Or looks at its next argument.
- Then names. cmp.Compare(a.Name, b.Name) compares "Ravi" with "Kiran". R comes after K, so it returns 1, the first result that is not 0.
- The answer. cmp.Or returns 1, so SortFunc puts b, Kiran, before a, Ravi. The three rows with 88 end up in name order, Bala, Kiran, Ravi.
- A second pair. Now a is Asha with 91 and b is Ravi with 88. cmp.Compare(88, 91) returns -1, which is not 0, so cmp.Or returns it, and the result for the names goes unused.
- Asha first. -1 puts a, Asha, before Ravi. The marks decide every pair whose marks differ, and the names decide only the ties.
Both comparisons are evaluated before cmp.Or runs, because they are its arguments, so a key that is costly to
compute belongs in an if that returns early instead. The package’s documentation adds one requirement, a strict
weak ordering: the same pair must always get the same answer, and if a comes before b and b before c, a must come
before c, or the sort may come out wrong.
Key idea
A comparison answers with a sign; cmp.Compare gives the sign for one key, and cmp.Or takes the first key that does not tie.
Checkpoint
Check: Comparisons on several keys
BinarySearch finds a value, or the place where it belongs
A binary search: A search of sorted data that looks at the middle element and drops the half that cannot hold the target, so n values need at most about log2 n looks. looks at the middle of a sorted slice and throws away the half that cannot hold the target, so 9 marks need at most 4 looks. Step through a search for 73 in these marks:
- being compared or looked at
- in its final place
- visited or ruled out
Pseudo-code
-
lo ← 0; hi ← n − 1 -
while lo ≤ hi: -
mid ← lo + ⌊(hi − lo) / 2⌋ -
if a[mid] = target: return mid -
if a[mid] < target: lo ← mid + 1 -
else: hi ← mid − 1 -
return "not found"
Look for 73. The invariant: if 73 is in the array, it is somewhere in a[lo..hi]. At the start that range is the whole array, positions 0 to 8.
When it finishes: Found 73 at index 5.
Try your own input
The figure found 73 at index 5 with three probes. slices.BinarySearch halves the range in the same way, but it keeps
going until it reaches the first position whose value is not below the target. That is why it can answer for a value
that is missing as well:
// binsearch.go: BinarySearch on sorted marks, for a value that is there and one that is not.
package main
import (
"fmt"
"slices"
)
func main() {
marks := []int{35, 42, 58, 61, 67, 73, 88, 91, 95}
i, found := slices.BinarySearch(marks, 73)
fmt.Println(i, found) // where 73 is
i, found = slices.BinarySearch(marks, 70)
fmt.Println(i, found) // where 70 would go
marks = slices.Insert(marks, i, 70) // inserting there keeps the slice sorted
fmt.Println(marks, slices.IsSorted(marks))
} Output
5 true 5 false [35 42 58 61 67 70 73 88 91 95] true
Recorded with Go 1.27.1 on macOS 26 arm64. To run it yourself: go run binsearch.go
For 73 it returned 5 true; for 70, 5 false: 70 is not there, and index 5 is where it would go. slices.Insert at
that index kept the slice sorted. The search trusts its input, though: on a slice that is not sorted it returns wrong
answers without any error.
Checkpoint
Check: Binary search and insertion
The package has more: slices.SortStableFunc is a stable sort: A sort that keeps equal elements in the order they had before it ran, such as slices.SortStableFunc. slices.SortFunc does not promise to., which keeps equal
elements in the order they had, as slices.SortFunc does not promise to. slices.Delete removes a range of elements
in place. slices.Compact removes repeated values that sit next to each other. slices.Replace puts new elements in
place of a range. slices.Equal reports whether two slices hold the same elements in the same order. The older sort
package sorts through an interface or a function that compares two indexes.
Stable sorts, comparisons that break, and the sort package, measured Pro: opens with a Pro, Premium or Ultimate pass
A sort can be correct and still surprise: ties that move between runs of a program, a comparison that overflows, a NaN that sorts first. This part reproduces each one on Go 1.27.1 and measures what the older sort package costs.
In this part:
- Ties reproduced: SortFunc moves equal elements, SortStableFunc keeps them
- A comparison that subtracts can overflow
- NaN sorts first, and Max returns NaN
- The sort package: an interface, a less function, and two allocations
5 runnable examples (Go) · about 5 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Delete, Compact, Insert and Equal: what each does to the backing array Pro: opens with a Pro, Premium or Ultimate pass
The editing functions of the slices package change the array they are given and return the slice to use from then on. This part shows what each one leaves behind for a caller who keeps the old slice, and builds a TopK that never touches its input.
In this part:
- slices.Delete zeroes the tail that the old slice can still see
- Compact removes runs; sort first to remove every repeat
- Insert, Replace and Equal
- TopK without touching the input
4 runnable examples (Go) · about 5 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Practise
Practice covers sorting by bytes, comparisons on several keys and binary search, starting with the idea your earlier answers left weakest. Each choice explains why it is right or what it misses:
Practice that adapts to you
Practise this lesson
8 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher): Why does slices.Sort put “Zoya” before “arjun”?
Strings compare byte by byte, and the byte of a capital letter is smaller than that of any small letter: “Z” is 90
and “a” is 97. So every name that starts with a capital comes first. For an order that ignores case, sort with
slices.SortFunc and compare lower-case forms of the names.
Three longer questions of the module bank build on this lesson:
-
Fresher to mid
A code review finds slices.SortFunc(accounts, func(a, b Account) int { return int(a.Balance - b.Balance) }) with int64 balances, where math.MinInt64 marks an account with no balance yet. The tests pass. What can go wrong, and what do you write instead?
-
Mid
A transactions page sorts its rows by date with slices.SortFunc. Users report that rows with the same date change places when they refresh, but the tests, which use ten rows, always pass. Explain both observations and fix it.
-
Mid to senior
A service checks each request against one million blocked ids kept in a sorted slice with slices.BinarySearch. A new feature appends ids to the end of the slice as they are reported, and some blocked ids now get through. Why, and how do you keep the check correct and fast?
Key takeaways
slices.Sortsorts any ordered type in place, and strings sort by their bytes, so capitals come before small letters.- A comparison returns a sign;
cmp.Comparegives it for one key, swapping its arguments reverses the order, andcmp.Orchains keys by returning the first result that is not zero. slices.BinarySearchreturns the position of the target, or where it belongs, and whether it was found; the slice must already be sorted.
Terms in this lesson
- binary search
- A search of sorted data that looks at the middle element and drops the half that cannot hold the target, so n values need at most about log2 n looks.
- comparison function
- A function of two elements, a and b, that returns a negative number when a sorts first, a positive one when b does, and 0 when they tie, as slices.SortFunc expects.
- stable sort
- A sort that keeps equal elements in the order they had before it ran, such as slices.SortStableFunc. slices.SortFunc does not promise to.
References
- Package slices (The Go Authors)
- Package cmp (The Go Authors)
- Package sort (The Go Authors)
- Go 1.21 Release Notes: New slices, maps and cmp packages (The Go Authors)
- Go 1.22 Release Notes: slices and cmp (The Go Authors)
- Robust generic functions on slices (The Go Authors)
- Source file slices/zsortanyfunc.go: the pattern-defeating quicksort behind SortFunc (The Go Authors)
- The Go Programming Language Specification: Comparison operators (The Go Authors)
- Package reflect: DeepEqual (The Go Authors)
- Package container/heap (The Go Authors)
Report a problem with this lesson
Kept only in this browser. Your Learn progress