Question

Sort by Multiple Fields in Kotlin: sortedBy, thenBy, and compareBy

Question

I have a list of Person objects and want to sort it by age first and then by name.

Coming from a C# background, I would normally do this with LINQ:

var list = new List<Person>();
list.Add(new Person(25, "Tom"));
list.Add(new Person(25, "Dave"));
list.Add(new Person(20, "Kate"));
list.Add(new Person(20, "Alice"));

// Result: Alice, Kate, Dave, Tom
var sortedList = list
    .OrderBy(person => person.Age)
    .ThenBy(person => person.Name)
    .ToList();

How can I do the equivalent in Kotlin?

I tried chaining sortedBy, but that does not work as expected because the second sort replaces the first one, so the final result is sorted only by name:

val sortedList = ArrayList(list.sortedBy { it.age }.sortedBy { it.name })

Short Answer

By the end of this page, you will understand how multi-field sorting works in Kotlin, why chaining sortedBy is usually the wrong approach for this problem, and how to correctly sort using compareBy, thenBy, and sortedWith. You will also see practical examples and common patterns used in real Kotlin codebases.

Concept

In Kotlin, sorting by multiple fields means defining a primary sort key and one or more secondary sort keys.

In your example:

  • Primary key: age
  • Secondary key: name

That means:

  1. Compare two people by age
  2. If their ages are different, use that result
  3. If their ages are the same, compare by name

Kotlin does this with a comparator. A comparator is an object or function that tells Kotlin how two values should be ordered.

The most common beginner-friendly way to sort by multiple fields is:

list.sortedWith(compareBy<Person> { it.age }.thenBy { it.name })

This creates a comparator that:

  • sorts by age first
  • then sorts by name when ages are equal

Why this matters in real programming:

  • You often sort users by last name then first name
  • You sort products by category then price
  • You sort log entries by date then severity
  • You sort API results by status then timestamp

Multi-field sorting is a very common data-handling task, so understanding comparator-based sorting is an important Kotlin skill.

Mental Model

Think of sorting like organizing papers in a filing cabinet.

  • First, you place papers into drawers by age
  • Inside each drawer, you arrange the papers by name

So if two people are both 20 years old, they go into the same age drawer, and then their names decide their order.

Using sortedBy twice is like first organizing by age, then taking the whole stack and reorganizing everything by name only. The second rule becomes the main visible order, which is not the same as saying “sort by age, then by name.”

A comparator is like a set of instructions for a clerk:

  1. Check age first
  2. If ages match, check name
  3. If names also match, treat them as equal for sorting
Take Quiz

Syntax and Examples

Core syntax

The standard Kotlin approach is:

val sortedList = list.sortedWith(
    compareBy<Person> { it.age }.thenBy { it.name }
)

Example

data class Person(val age: Int, val name: String)

fun main() {
    val list = listOf(
        Person(25, "Tom"),
        Person(25, "Dave"),
        Person(20, "Kate"),
        Person(20, "Alice")
    )

    val sortedList = list.sortedWith(
        compareBy<Person> { it.age }.thenBy { it.name }
    )

    println(sortedList)
}

Output:

[Person(age=20, name=Alice), Person(age=20, name=Kate), Person(age=25, name=Dave), Person(age=25, name=Tom)]

Shorter form

Kotlin also supports passing multiple selectors to compareBy:

val sortedList = list.sortedWith(compareBy<Person>({ it.age }, { it.name }))

This means the same thing:

  • compare by age
  • if equal, compare by name

Descending order

If you want age descending and name ascending:

val sortedList = list.sortedWith(
    compareByDescending<Person> { it.age }.thenBy { it.name }
)

Mutable list sorting

If you want to sort the same mutable list in place:

val people = mutableListOf(
    Person(25, "Tom"),
    Person(25, "Dave"),
    Person(20, "Kate"),
    Person(20, "Alice")
)

people.sortWith(compareBy<Person> { it.age }.thenBy { it.name })

Step by Step Execution

Consider this code:

data class Person(val age: Int, val name: String)

val list = listOf(
    Person(25, "Tom"),
    Person(25, "Dave"),
    Person(20, "Kate"),
    Person(20, "Alice")
)

val sortedList = list.sortedWith(
    compareBy<Person> { it.age }.thenBy { it.name }
)

Here is what the comparator does step by step:

1. Compare Person(25, "Tom") and Person(25, "Dave")

  • Ages are both 25
  • Since ages are equal, compare names
  • "Dave" comes before "Tom"

2. Compare Person(20, "Kate") and Person(25, "Dave")

  • 20 is less than 25
  • So Person(20, "Kate") comes first
  • No need to compare names

3. Compare Person(20, "Alice") and Person(20, "Kate")

  • Ages are both 20
  • Compare names
  • "Alice" comes before "Kate"

Final result

[
    Person(20, "Alice"),
    Person(20, "Kate"),
    Person(25, "Dave"),
    Person(25, "Tom")
]

Why sortedBy(...).sortedBy(...) is misleading

val wrong = list.sortedBy { it.age }.sortedBy { it.name }

This performs two full sorts:

  1. first by age
  2. then by name

The final visible ordering is driven by the second sort expression. That does not clearly express a true multi-key comparator and is not the recommended way to solve this problem.

For readable, intentional code, use sortedWith(compareBy(...).thenBy(...)).

Real World Use Cases

Multi-field sorting appears in many real applications.

User lists

Sort users by:

  • lastName, then firstName
  • role, then username
  • createdAt, then id

E-commerce

Sort products by:

  • category, then price
  • availability, then rating
  • brand, then name

Logging and monitoring

Sort log entries by:

  • date, then severity
  • service, then timestamp

Reporting and analytics

Sort rows by:

  • department, then salary
  • country, then city

API result formatting

Before returning data from a service, developers often sort by multiple fields to make output predictable and easier to consume.

Take Quiz

Real Codebase Usage

In real Kotlin projects, developers usually use comparator chains for clarity and maintainability.

Common pattern: comparator chain

val personComparator = compareBy<Person> { it.age }
    .thenBy { it.name }

This is useful when the same ordering is reused in several places.

Sorting before display

val visibleUsers = users
    .filter { it.isActive }
    .sortedWith(compareBy<User> { it.lastName }.thenBy { it.firstName })

This pattern often appears in UI code, reports, and APIs.

Guarding against null values

When some fields may be null, developers often decide on a rule explicitly.

data class User(val age: Int?, val name: String)

val sorted = users.sortedWith(
    compareBy<User> { it.age ?: Int.MAX_VALUE }
        .thenBy { it.name }
)

This pushes users with unknown age to the end.

In-place sorting for mutable collections

people.sortWith(compareBy<Person> { it.age }.thenBy { it.name })

This is common when working with MutableList.

Domain-specific comparators

Teams often define named comparators:

val byPriorityThenDate = compareBy<Task> { it.priority }
    .thenBy { it.createdAt }

This makes business rules easier to read and test.

Common Mistakes

1. Chaining sortedBy and expecting true multi-key sorting

Broken idea:

val sorted = list.sortedBy { it.age }.sortedBy { it.name }

Why it is a problem:

  • it does not clearly express primary and secondary keys
  • it performs multiple sorts
  • it is not the standard Kotlin solution for multi-field sorting

Use this instead:

val sorted = list.sortedWith(compareBy<Person> { it.age }.thenBy { it.name })

2. Forgetting sortedBy returns a new list

list.sortedBy { it.age }
println(list) // original list is unchanged

Fix:

val sorted = list.sortedBy { it.age }

Or use sortBy / sortWith for mutable lists.

3. Mixing mutable and immutable APIs

  • sortedBy, sortedWith -> return a new list
  • sortBy, sortWith -> modify a mutable list in place

Example:

val people = mutableListOf(Person(25, "Tom"), Person(20, "Alice"))
people.sortWith(compareBy<Person> { it.age }.thenBy { it.name })

4. Not thinking about null values

Broken code if age can be null:

data class Person(val age: Int?, val name: String)

val sorted = people.sortedWith(compareBy<Person> { it.age }.thenBy { it.name })

This may not match the ordering you want.

Be explicit:

val sorted = people.sortedWith(
    compareBy<Person> { it.age ?: Int.MAX_VALUE }.thenBy { it.name }
)

5. Forgetting ascending vs descending rules

If one field should be descending, do not try to force it manually in confusing ways.

Use the built-in helpers:

val sorted = people.sortedWith(
    compareByDescending<Person> { it.age }.thenBy { it.name }
)

Comparisons

ApproachWhat it doesGood forNotes
sortedBy { ... }Sorts by one keySimple single-field sortingReturns a new list
sortedWith(compareBy(...).thenBy(...))Sorts by multiple keysClear multi-field sortingMost common solution
sortBy { ... }Sorts one key in placeMutable listsChanges original list
sortWith(compareBy(...).thenBy(...))Sorts multiple keys in placeMutable listsChanges original list

sortedBy vs sortedWith

val byAge = list.sortedBy { it.age }
val byAgeThenName = list.sortedWith(compareBy<Person> { it.age }.thenBy { it.name })
  • Use sortedBy when there is only one sort field.
  • Use sortedWith when sorting logic needs multiple fields or custom comparison rules.

Kotlin vs C# LINQ

C#Kotlin
OrderBy(x => x.Age).ThenBy(x => x.Name)sortedWith(compareBy<Person> { it.age }.thenBy { it.name })
OrderByDescending(...)compareByDescending { ... }
ToList()Usually not needed if you already have a list result

Both languages support the same idea: define a primary key, then fallback keys.

Take Quiz

Cheat Sheet

Multi-field sorting in Kotlin

New sorted list

val sorted = list.sortedWith(
    compareBy<Person> { it.age }.thenBy { it.name }
)

Equivalent compact form

val sorted = list.sortedWith(compareBy<Person>({ it.age }, { it.name }))

Sort mutable list in place

people.sortWith(compareBy<Person> { it.age }.thenBy { it.name })

Descending + ascending

val sorted = list.sortedWith(
    compareByDescending<Person> { it.age }.thenBy { it.name }
)

Single-field sort

val sorted = list.sortedBy { it.age }

Remember

  • sortedBy and sortedWith return a new list
  • sortBy and sortWith modify a mutable list
  • Use thenBy for secondary sorting
  • Use compareBy to build multi-key comparators
  • Be explicit with null handling when needed

FAQ

How do I sort by multiple properties in Kotlin?

Use sortedWith together with compareBy and thenBy:

val sorted = list.sortedWith(compareBy<Person> { it.age }.thenBy { it.name })

Can I chain sortedBy in Kotlin?

You can, but it is not the recommended way to express multi-field sorting. Use a comparator chain with compareBy(...).thenBy(...) instead.

What is the Kotlin equivalent of C# OrderBy().ThenBy()?

The closest equivalent is:

list.sortedWith(compareBy<Person> { it.age }.thenBy { it.name })

What is the difference between sortedWith and sortWith?

  • sortedWith returns a new sorted list
  • sortWith modifies a mutable list in place

How do I sort descending in Kotlin?

Use compareByDescending or reverse the comparator:

val sorted = list.sortedWith(compareByDescending<Person> { it.age })

Can I sort by more than two fields?

Yes:

val sorted = list.sortedWith(
    compareBy<Person> { it.age }
        .thenBy { it.name }
        .thenBy { it.id }
)

Does Kotlin sorting work with data classes?

Yes. Data classes work very well for sorting because their properties are easy to access in comparator expressions.

Take Quiz

Mini Project

Description

Build a small Kotlin program that sorts a list of students by grade first and then by name. This demonstrates how to define a multi-field sort rule that produces predictable output when multiple students share the same grade.

Goal

Create a program that prints students ordered by ascending grade, and alphabetically by name when grades are equal.

Requirements

[ "Create a Student data class with grade and name properties", "Build a list containing at least five students", "Sort the list by grade first and then by name", "Print the original list and the sorted list", "Use Kotlin comparator-based sorting rather than chaining sortedBy twice" ]

Take Quiz

Keep learning

Related questions

View more