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:
- Compare two people by
age - If their ages are different, use that result
- 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
agefirst - then sorts by
namewhen 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:
- Check age first
- If ages match, check name
- If names also match, treat them as equal for sorting
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:
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")
20is less than25- So comes first
Real World Use Cases
Multi-field sorting appears in many real applications.
User lists
Sort users by:
lastName, thenfirstNamerole, thenusernamecreatedAt, thenid
E-commerce
Sort products by:
category, thenpriceavailability, thenratingbrand, thenname
Logging and monitoring
Sort log entries by:
date, thenseverityservice, thentimestamp
Reporting and analytics
Sort rows by:
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
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
Comparisons
| Approach | What it does | Good for | Notes |
|---|---|---|---|
sortedBy { ... } | Sorts by one key | Simple single-field sorting | Returns a new list |
sortedWith(compareBy(...).thenBy(...)) | Sorts by multiple keys | Clear multi-field sorting | Most common solution |
sortBy { ... } | Sorts one key in place | Mutable lists | Changes original list |
sortWith(compareBy(...).thenBy(...)) | Sorts multiple keys in place | Mutable lists | Changes original list |
sortedBy vs sortedWith
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
sortedByandsortedWithreturn a new listsortByandsortWithmodify a mutable list- Use
thenByfor secondary sorting - Use
compareByto build multi-key comparators
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?
sortedWithreturns a new sorted listsortWithmodifies a mutable list in place
How do I sort descending in Kotlin?
Use compareByDescending or reverse the comparator:
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" ]
Keep learning
Related questions
Android AlarmManager Example: Scheduling Tasks with AlarmManager
Learn how to use Android AlarmManager to schedule tasks, set alarms, and handle broadcasts with a simple beginner example.
Can You Extend a Data Class in Kotlin? Inheritance, Limits, and Better Alternatives
Learn why Kotlin data classes cannot be extended, what causes the component function clash, and which alternatives to use instead.
Difference Between List and Array in Kotlin
Learn the difference between List and Array in Kotlin, including mutability, size, APIs, performance, and when to use each one.