{
"$type": "site.standard.document",
"canonicalUrl": "https://rednafi.com/go/sort-slice/",
"description": "Compare three Go slice sorting methods: sort.Interface, sort.Slice with closures, and modern generic slices.Sort with type safety.",
"path": "/go/sort-slice/",
"publishedAt": "2025-03-22T00:00:00.000Z",
"site": "at://did:plc:fgtm2c26vfcj74rfmeggbyqj/site.standard.publication/3mnl6f7ob462z",
"tags": [
"Go",
"Data Structures"
],
"textContent": "There are primarily three ways of sorting slices in Go. Early on, we had the verbose but\nflexible method of implementing sort.Interface to sort the elements in a slice. Later, Go\n1.8 introduced sort.Slice to reduce boilerplate with inline comparison functions. Most\nrecently, Go 1.21 brought generic sorting via the slices package, which offers a concise\nsyntax and compile-time type safety.\n\nThese days, I mostly use the generic sorting syntax, but I wanted to document all three\napproaches for posterity.\n\nUsing sort.Interface\n\nThe oldest technique is based on sort.Interface. You create a custom type that wraps your\nslice and implement three methods - Len, Less, and Swap - to satisfy the interface.\nThen you pass this custom type to sort.Sort().\n\nSorting a slice of integers\n\nThe following example defines an IntSlice type. Passing an IntSlice to sort.Sort\narranges its integers in ascending order:\n\nTo reverse the order, invert the comparison in the Less method and define a new type:\n\nJust reversing the order requires you to define a separate type and implement the three\nmethods again!\n\nLuckily, for the basic types, the sort package provides sort.IntSlice,\nsort.Float64Slice, and sort.StringSlice - which already implement sort.Interface. So\nyou don't have to do the above for sorting a slice of primitive elements. Instead, you can\ndo this:\n\nTo reverse the order, you can use sort.Reverse as follows:\n\nSorting a slice of structs by age\n\nHowever, if you're dealing with a slice of structs, then you do have to implement\nsort.Interface manually. Here, we sort by the Age field in ascending order:\n\nWe can leverage sort.Reverse to reverse the order:\n\nAlthough sort.Interface can handle just about any sorting logic, you must create a new\ncustom type (or significantly modify an existing one) each time you want to sort a different\nslice or the same slice in a different way. It's powerful but verbose, and can be cumbersome\nto maintain if you have many different sorts in your code.\n\nUsing sort.Slice\n\nGo 1.8 introduced sort.Slice to minimize the amount of boilerplate needed for sorting.\nInstead of creating a new type and implementing three methods, you provide an inline\ncomparison function that receives the two indices you're comparing.\n\nSorting a slice of float64\n\nHere's a simple example that sorts floats in ascending order:\n\nInverting the comparison sorts them in descending order:\n\nSorting a slice of structs by age\n\nFor structs, the inline comparator can access struct fields:\n\nSwitching > for < will reverse the sort:\n\nWhile sort.Slice is much simpler than sort.Interface, it's still not strictly type-safe:\nthe slice parameter is defined as an interface{}, and you provide a comparator that uses\nindices. Go won't necessarily stop you from doing something incorrect in the comparison at\ncompile time.\n\nFor example, this code compiles but will panic at runtime because other is referenced\ninside the comparator of a different slice ints, and the indices i or j can go out of\nbounds in other:\n\nYou won't find out you've made a mistake until runtime, when a panic occurs. There is no\ncompiler-enforced guarantee that the func(i, j int) bool actually compares two values of\nthe intended slice.\n\nNote: In sort.Slice, the comparison function parameters i and j are _indices_.\nInside the function, you must reference slice[i] and slice[j] to get the actual elements\nbeing compared.\n\nUsing generics with the slices package\n\nGo 1.21 introduced the slices package, which provides generic sorting functions. These new\nfunctions combine the convenience of sort.Slice with the ability to detect type errors at\ncompile time. For basic numeric or string slices that satisfy Go's \"ordered\" constraints,\nyou can just call slices.Sort. For more complex or custom sorting, slices.SortFunc\naccepts a comparator function that returns an integer (negative if a < b, zero if they're\nequal, and positive if a > b).\n\nSorting primitive slices\n\nWhen you're dealing with basic types like int, float64, or string, you can sort them\nimmediately using slices.Sort, which arranges them in ascending order:\n\nFor descending order, you can use slices.SortFunc and invert the usual comparison:\n\nSorting a slice of structs by age\n\nWhen dealing with more complex structures, you can define precisely how two elements should\nbe compared:\n\nTo reverse the order, invert the numerical comparison:\n\nNote: Unlike sort.Slice, which passes indices to the comparison function,\nslices.SortFunc passes the actual elements (a and b) to your comparator. Moreover,\nthe comparator must return an int (negative, zero, or positive), rather than a boolean.\n\nCompile-time safety\n\nOne of the major benefits of the slices package is compile-time type safety, which you\ndon't get with sort.Sort or sort.Slice. Those older APIs use interface{} parameters or\nindex-based comparators and don't strictly verify that your comparator operates on the right\ntypes.\n\nAs shown previously, you can accidentally reference a different slice in the comparator and\nyour code will compile but crash at runtime. By contrast, slices.Sort and\nslices.SortFunc are fully generic. The compiler enforces that you pass a slice of a valid\ntype (e.g., []int, []string, or a custom struct slice), and that your comparator's\nsignature matches the element type. This means you get errors at compile time instead of at\nruntime.\n\nFor instance, if you attempt to pass an array instead of a slice:\n\nGo will refuse to compile this code because arr is not a slice. Similarly, if your\ncomparator for slices.SortFunc returns a type other than int, the compiler will produce\nan error. This helps you detect mistakes immediately, rather than discovering them in\nruntime.\n\nFor a practical illustration, consider sorting a slice by a case-insensitive string field:\n\nBecause your comparator expects an Animal for both a and b, you can't accidentally\ncompare two different types or reference the wrong fields without hitting a compile-time\nerror.",
"title": "Three flavors of sorting Go slices"
}