Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
126: sort.Slice and Custom Sorting
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)
}
}
There are no comments for now.