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
89: Sorting Custom Types with sort.Interface
Up until now, we've probably been using sort.Ints or sort.Strings. Those are great for primitives, but in a real production codebase, you're rarely sorting just a list of integers. You're sorting users by their last login date, products by price, or—in the case we're looking at today—servers by their current CPU load.
To sort a custom collection, Go provides the sort.Interface. It's a bit old-school compared to some newer generic functions, but it's the foundation of how sorting works in Go. To satisfy this interface, your type needs three methods: Len(), Less(), and Swap().
Defining the Server collection
Let's imagine we're building a basic monitoring tool. We have a Server struct, and we want to sort a list of them so the most stressed server appears at the top. First, we define our data.
type Server struct {
Name string
Load float64
}
// I'm creating a named type for the slice here.
// This is the part most people forget: you can't attach methods to
// a raw slice like []Server. You need a defined type.
type ServerList []Server
Implementing the sort.Interface contract
Now we have to tell Go how to handle ServerList. The sort package doesn't know what a "Server" is or how to compare "Load"; it just knows how to call these three specific methods.
func (s ServerList) Len() int {
return len(s)
}
func (s ServerList) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s ServerList) Less(i, j int) bool {
return s[i].Load < s[j].Load
}
At this point, ServerList implicitly implements sort.Interface. We can now pass it directly into sort.Sort().
Correcting the sorting direction
I just ran a quick test with this code, and I realized I made a classic mistake. Because I used s[i].Load < s[j].Load in my Less method, sort.Sort() gave me the servers in ascending order. The server with the lowest load is at the top. That's the opposite of what a monitoring dashboard needs; I want the "hottest" server first.
The trick here is that Less doesn't literally have to mean "less than" in a mathematical sense—it just defines which element should come first. To get a descending sort, I just need to flip the comparison operator.
func (s ServerList) Less(i, j int) bool {
// Flipping this to > ensures the higher load comes first
return s[i].Load > s[j].Load
}
Putting it all together
Now that the logic is corrected, we can actually use it. I'll initialize a few servers and call the sort function. Notice how sort.Sort takes the interface, not the slice itself, though since ServerList is a slice, it works seamlessly.
func main() {
servers := ServerList{
{"web-prod-01", 0.45},
{"web-prod-02", 0.92},
{"db-master", 0.78},
{"cache-01", 0.12},
}
sort.Sort(servers)
for _, s := range servers {
fmt.Printf("%s: %.2f\n", s.Name, s.Load)
}
}
// Output:
// web-prod-02: 0.92
// db-master: 0.78
// web-prod-01: 0.45
// cache-01: 0.12
It's a bit of boilerplate—writing Len, Less, and Swap every time feels tedious—but it's incredibly performant because it avoids the overhead of reflection or frequent interface casting during the sort loop.
📋 Practical Task
Build a High-Value Customer Sorter
You are tasked with creating a reporting tool for a sales team. They need to sort a list of customers based on their total spend, from highest to lowest (descending).
Requirements:
- Create a
Customerstruct withName(string) andTotalSpend(float64). - Define a custom slice type
CustomerList. - Implement the
sort.Interface(Len,Less, andSwap) forCustomerListso that customers with the highest spend appear first. - In your
mainfunction, create aCustomerListwith at least four different customers and usesort.Sort()to order them. - Print the sorted list to the console to verify the order.
There are no comments for now.