Skip to Content
Course content

126: sort.Slice and Custom Sorting

Click on the "Edit" button in the top corner of the screen to edit your slide content.

If you've been looking at older Go documentation or legacy codebases, you've likely seen a pattern where someone defines a new type—like type ByAge []Person—and then implements three different methods: Len(), Less(), and Swap(). Because of this, a lot of learners walk away thinking that this is the "proper" way to sort custom data in Go, and that implementing this interface is a mandatory tax you pay every time you want to sort a slice of structs.

It's not. At least, not anymore.

You don't have to implement sort.Interface every time

For most of the tasks you'll actually do in a production environment, implementing sort.Interface is overkill. It's tedious, it clutters your types, and it's easy to mess up. Instead, we have sort.Slice. This function lets you provide a "less" function as a closure, which tells Go exactly how to compare two elements without needing to define a whole new type.

Let's look at a real-world example. Imagine you're building a small inventory system for a bookstore. You have a Book struct, and you want to sort your list by price.

package main

import (
	"fmt"
	"sort"
)

type Book struct {
	Title  string
	Price  float64
	Author string
}

func main() {
	books := []Book{
		{"The Go Programming Language", 34.99, "Donovan & Kernighan"},
		{"Clean Code", 42.50, "Robert C. Martin"},
		{"Concurrency in Go", 28.00, "Katherine Cox-Buday"},
	}

	// Instead of creating a 'ByPrice' type and 3 methods, just do this:
	sort.Slice(books, func(i, j int) bool {
		return books[i].Price < books[j].Price
	})

	fmt.Println(books)
}

I love this approach because the sorting logic lives exactly where it's being used. If I suddenly decide I want to sort by Title instead, I just change one line of code inside the closure rather than hunting for a type definition elsewhere in the file.

The "Less" function is for strict ordering, not equality

Here is where people usually trip up: the boolean return value of the closure. You'll often see developers try to handle "equal" cases inside the Less function by returning true when books[i].Price == books[j].Price. Don't do that.

The sort.Slice function expects a strict weak ordering. In plain English: the function should return true if and only if the element at index i must come before the element at index j. If they are equal, it must return false. If you return true for equal elements, you're lying to the sorting algorithm about the relationship between the items, which can lead to inconsistent results or even crashes in some sorting implementations.

Choosing between Slice and SliceStable

One thing I've noticed in code reviews is that developers often use sort.Slice when they actually need sort.SliceStable. The difference is subtle but critical: sort.Slice does not guarantee that the original order of "equal" elements is preserved. It might swap them around to be more efficient.

Think about a list of users already sorted by Join Date. Now you want to sort them by Membership Level (Gold, Silver, Bronze). If you use sort.Slice, two "Gold" members might have their relative order flipped. If you use sort.SliceStable, Go guarantees that those two Gold members will stay in the same relative order they were in before the sort (meaning they remain sorted by Join Date within their membership tier).

My rule of thumb? Use sort.Slice by default for performance, but switch to sort.SliceStable the second you realize you're performing a multi-pass sort or if the original input order carries meaning.




📋 Practical Task

Multi-Tier Employee Directory Sort

You are building an employee directory. You have a slice of Employee structs, and you need to sort them using a specific business rule: Employees must be sorted primarily by Department (alphabetically), and secondarily by Years of Experience (highest experience first).

Implement the sorting logic using sort.Slice. Ensure that if two employees are in the same department, the one with more experience appears first.

package main

import (
	"fmt"
	"sort"
)

type Employee struct {
	Name       string
	Department string
	Experience int
}

func main() {
	employees := []Employee{
		{"Alice", "Engineering", 5},
		{"Bob", "Sales", 3},
		{"Charlie", "Engineering", 8},
		{"Diana", "Sales", 10},
		{"Eve", "HR", 2},
		{"Frank", "Engineering", 2},
	}

	// TODO: Implement sort.Slice here to sort by Department (ASC) 
	// and then by Experience (DESC)

	fmt.Println("Sorted Employees:")
	for _, e := range employees {
		fmt.Printf("%s - %s (%d years)\n", e.Department, e.Name, e.Experience)
	}
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.