Question

How to Sort a Slice of Structs by Field in Go

gosorting

Question

I have a collection of structs in Go, for example:

package main

import "log"

type Planet struct {
    Name       string  `json:"name"`
    Aphelion   float64 `json:"aphelion"`   // in million km
    Perihelion float64 `json:"perihelion"` // in million km
    Axis       int64   `json:"axis"`       // in km
    Radius     float64 `json:"radius"`
}

func main() {
    mars := Planet{
        Name:       "Mars",
        Aphelion:   249.2,
        Perihelion: 206.7,
        Axis:       227939100,
        Radius:     3389.5,
    }

    earth := Planet{
        Name:       "Earth",
        Aphelion:   151.930,
        Perihelion: 147.095,
        Axis:       149598261,
        Radius:     6371.0,
    }

    venus := Planet{
        Name:       "Venus",
        Aphelion:   108.939,
        Perihelion: 107.477,
        Axis:       108208000,
        Radius:     6051.8,
    }

    planets := []Planet{mars, venus, earth}
    log.Println(planets)
}

How can I sort this slice by a field such as Axis?

I found Go's sort package, but some approaches seem verbose compared with Python's sorted(planets, key=lambda p: p.Axis). Is there a shorter and idiomatic way in Go to sort a slice of structs by a chosen field?

Short Answer

By the end of this page, you will understand how sorting structs works in Go, why Go uses comparison functions instead of a key= parameter like Python, and the simplest idiomatic way to sort a slice of structs by fields such as Axis, Name, or Radius. You will also learn how in-place sorting works, how to sort ascending or descending, and what mistakes beginners often make.

Concept

In Go, sorting is usually done by telling the language how to compare two elements.

For a slice of structs, Go does not automatically know which field you want to sort by. A struct like Planet has multiple fields, so you must provide the sorting rule.

The most common modern approach is to use sort.Slice, which lets you pass a slice and a comparison function:

sort.Slice(planets, func(i, j int) bool {
    return planets[i].Axis < planets[j].Axis
})

This means:

  • compare the element at index i with the element at index j
  • return true if element i should come before element j

This approach matters because real programs often need custom sorting:

  • sort users by age
  • sort products by price
  • sort API results by timestamp
  • sort records alphabetically by name

Unlike Python's key= style, Go prefers explicit comparison logic. That can feel slightly more verbose at first, but it is flexible and easy to read once you get used to it.

Mental Model

Think of sorting in Go like giving instructions to a librarian.

You hand the librarian a pile of books and say:

  • "Put the book with the smaller page count first"
  • or "Put the book with the earlier publication date first"

Go's sort.Slice works the same way. It keeps asking your comparison function:

  • "Should item i come before item j?"

Your function answers with true or false.

So instead of saying extract this key, you say compare these two items using this field.

Take Quiz

Syntax and Examples

The basic syntax for sorting a slice of structs is:

sort.Slice(sliceName, func(i, j int) bool {
    return sliceName[i].Field < sliceName[j].Field
})

Example: sort by Axis

package main

import (
    "fmt"
    "sort"
)

type Planet struct {
    Name string
    Axis int64
}

func main() {
    planets := []Planet{
        {Name: "Mars", Axis: 227939100},
        {Name: "Venus", Axis: 108208000},
        {Name: "Earth", Axis: 149598261},
    }

    sort.Slice(planets, func(i, j int) bool {
        return planets[i].Axis < planets[j].Axis
    })

    fmt.Println(planets)
}

Output order:

[{Venus 108208000} {Earth 149598261} {Mars 227939100}]

Sort by Name

sort.Slice(planets, func(i, j int) bool {
    return planets[i].Name < planets[j].Name
})

Sort descending

sort.Slice(planets, func(i, j int) bool {
    return planets[i].Axis > planets[j].Axis
})

Important note

sort.Slice sorts the slice in place. It changes the original slice instead of returning a new one.

Step by Step Execution

Consider this example:

planets := []Planet{
    {Name: "Mars", Axis: 227939100},
    {Name: "Venus", Axis: 108208000},
    {Name: "Earth", Axis: 149598261},
}

sort.Slice(planets, func(i, j int) bool {
    return planets[i].Axis < planets[j].Axis
})

Here is what happens step by step:

  1. sort.Slice receives the planets slice.

  2. Go starts comparing elements at different indexes.

  3. Suppose it compares Mars and Venus.

  4. The function checks:

    planets[i].Axis < planets[j].Axis
    
  5. For Mars (227939100) and Venus (108208000), the result is false.

  6. That means Mars should not come before Venus.

  7. Go continues comparing other pairs.

  8. After enough comparisons and swaps, the slice becomes ordered by Axis.

Final result:

[]Planet{
    {Name: "Venus", Axis: 108208000},
    {Name: "Earth", Axis: 149598261},
    {Name: "Mars", Axis: 227939100},
}

You do not manually swap elements yourself. You only provide the rule.

Real World Use Cases

Sorting structs by a field is common in everyday Go programs.

API responses

You might fetch data from a database or external API and sort it before returning JSON:

  • users by signup date
  • orders by total amount
  • blog posts by publish time

CLI tools

A command-line tool might sort:

  • files by size
  • processes by memory usage
  • tasks by priority

Dashboards and admin panels

Backend code often sorts records before rendering or sending them to the frontend:

  • products by price
  • employees by last name
  • planets by radius or distance

Data processing

When analyzing data, sorting helps with:

  • ranking results
  • finding smallest/largest items
  • preparing reports in a predictable order
Take Quiz

Real Codebase Usage

In real Go codebases, developers often use sorting in a few standard patterns.

1. Inline sorting with sort.Slice

This is the most common choice for one-off sorts:

sort.Slice(users, func(i, j int) bool {
    return users[i].CreatedAt.Before(users[j].CreatedAt)
})

Good when the sort rule is simple and used only once.

2. Reusable helper functions

If you sort by the same field in many places, create a helper:

func SortPlanetsByAxis(planets []Planet) {
    sort.Slice(planets, func(i, j int) bool {
        return planets[i].Axis < planets[j].Axis
    })
}

This improves readability and reuse.

3. Guard clauses before sorting

Developers may skip unnecessary work:

if len(planets) < 2 {
    return
}

4. Multi-field sorting

If two values are equal, compare another field:

sort.Slice(planets, func(i, j int) bool {
    if planets[i].Axis == planets[j].Axis {
        return planets[i].Name < planets[j].Name
    }
    return planets[i].Axis < planets[j].Axis
})

5. Sorting after validation or filtering

A common pattern is:

  • validate input
  • filter data
  • sort results
  • return response

For example:

sort.Slice(products, func(i, j int) bool {
    return products[i].Price < products[j].Price
})

Common Mistakes

1. Expecting a new slice to be returned

This is a common mistake for people coming from Python.

Broken expectation:

sortedPlanets := sort.Slice(planets, func(i, j int) bool {
    return planets[i].Axis < planets[j].Axis
})

Problem:

  • sort.Slice does not return the sorted slice
  • it modifies the original slice in place

Correct usage:

sort.Slice(planets, func(i, j int) bool {
    return planets[i].Axis < planets[j].Axis
})

2. Using an array instead of a slice without thinking about it

The original example used an array-like form:

planets := [...]Planet{mars, venus, earth}

This creates an array, not a slice. While slices are the most common thing to sort in Go.

Prefer:

planets := []Planet{mars, venus, earth}

3. Writing inconsistent comparison logic

Broken example:

sort.Slice(planets, func(i, j int) bool {
    return planets[i].Axis <= planets[j].Axis
})

Why this is bad:

  • sorting functions expect a strict ordering
  • use < for ascending or > for descending
  • <= can lead to incorrect or unstable logic

Correct:

return planets[i].Axis < planets[j].Axis

4. Accidentally comparing the wrong field

Broken example:

sort.Slice(planets, func(i, j int) bool {
    return planets[i].Radius < planets[j].Axis
})

This compares unrelated values and gives meaningless ordering.

5. Forgetting stable sorting when order of equal items matters

If two items can have the same field value and you want to preserve their original order, use sort.SliceStable.

sort.SliceStable(planets, func(i, j int) bool {
    return planets[i].Axis < planets[j].Axis
})

Comparisons

ApproachHow it worksBest forNotes
sort.SlicePass a slice and comparison functionMost struct sorting in modern GoShort and idiomatic
sort.SliceStableLike sort.Slice, but keeps equal items in original orderWhen stability mattersSlightly different behavior
Custom sort.InterfaceDefine Len, Less, and Swap methodsReusable named sort typesMore verbose
Python sorted(..., key=...)Pass a key extraction functionConcise key-based sortingDifferent language style

sort.Slice vs custom sort.Interface

sort.Slice

sort.Slice(planets, func(i, j int) bool {
    return planets[i].Axis < planets[j].Axis
})

Use this when:

  • you want the shortest idiomatic solution
  • the sort rule is local and simple

sort.Interface

type ByAxis []Planet

func (p ByAxis) Len() int           { return len(p) }
func (p ByAxis) Less(i, j int) bool { return p[i].Axis < p[j].Axis }
func (p ByAxis) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

Use this when:

  • you want a reusable named sorter
  • you need the same sort behavior in many places
  • you want compatibility with older Go style
Take Quiz

Cheat Sheet

import "sort"

Sort a slice of structs ascending

sort.Slice(planets, func(i, j int) bool {
    return planets[i].Axis < planets[j].Axis
})

Sort descending

sort.Slice(planets, func(i, j int) bool {
    return planets[i].Axis > planets[j].Axis
})

Stable sort

sort.SliceStable(planets, func(i, j int) bool {
    return planets[i].Axis < planets[j].Axis
})

Sort by string field

sort.Slice(planets, func(i, j int) bool {
    return planets[i].Name < planets[j].Name
})

Multi-field sort

sort.Slice(planets, func(i, j int) bool {
    if planets[i].Axis == planets[j].Axis {
        return planets[i].Name < planets[j].Name
    }
    return planets[i].Axis < planets[j].Axis
})

Key rules

  • sort.Slice modifies the original slice
  • use a []Type slice for normal Go collection sorting
  • comparison should usually use < or >
  • use sort.SliceStable if equal items must keep their original order

FAQ

Is there a Python-like key= argument in Go?

Not in the same form. In Go, the normal pattern is to provide a comparison function with sort.Slice.

What is the simplest way to sort a slice of structs in Go?

Use sort.Slice with a small comparison function.

sort.Slice(items, func(i, j int) bool {
    return items[i].Field < items[j].Field
})

Does sort.Slice return a new slice?

No. It sorts the existing slice in place.

Can I sort by multiple fields?

Yes. Compare the primary field first, and if equal, compare a secondary field.

Should I use sort.Slice or sort.Interface?

Use sort.Slice for most cases. Use sort.Interface when you want a reusable named sorting type.

How do I sort in descending order?

Reverse the comparison:

return items[i].Field > items[j].Field

What if equal items should keep their original order?

Use sort.SliceStable instead of sort.Slice.

Take Quiz

Mini Project

Description

Build a small Go program that stores planets in a slice and lets you sort them by different fields. This demonstrates how to sort structs by numeric and string fields using idiomatic Go.

Goal

Create a program that sorts a slice of Planet structs by Axis, Radius, and Name, then prints the results.

Requirements

Define a Planet struct with at least Name, Axis, and Radius fields. Create a slice containing at least four planets. Sort the slice by Axis in ascending order. Sort the slice by Radius in descending order. Sort the slice by Name alphabetically and print each result.

Take Quiz

Keep learning

Related questions

View more