# How to Use Kotlin sortedBy for Multi-Field Sorting with Mixed Ascending and Descending Order

> Learn to sort Kotlin collections by multiple fields using sortedWith and chained thenBy thenByDescending operators for mixed ascending descending order. Optimize your data.

- Repository: [JetBrains/kotlin](https://github.com/jetbrains/kotlin)
- Tags: how-to-guide
- Published: 2026-02-20

---

**Use `sortedWith` combined with `compareBy` and chained `thenBy` or `thenByDescending` operators to sort Kotlin collections by multiple fields with mixed sort directions.**

While `kotlin sortedBy` provides a concise way to sort by a single field, sorting by multiple criteria requires composing comparators. The Kotlin standard library implements this through a hierarchy of comparator-building functions that delegate to stable sorting algorithms under the hood.

## Why Kotlin sortedBy Cannot Sort by Multiple Fields Alone

The `sortedBy` extension function is designed for single-field sorting. According to the implementation in [`libraries/stdlib/common/src/generated/_Collections.kt`](https://github.com/JetBrains/kotlin/blob/main/libraries/stdlib/common/src/generated/_Collections.kt) (lines 1067-1080), `sortedBy` delegates directly to `sortedWith(compareBy(selector))` and returns a new **List**, not a comparator:

```kotlin
// From _Collections.kt lines 1067-1080
public inline fun <T, R : Comparable<R>> Iterable<T>.sortedBy(crossinline selector: (T) -> R?): List<T> {
    return sortedWith(compareBy(selector))
}

public inline fun <T, R : Comparable<R>> Iterable<T>.sortedByDescending(crossinline selector: (T) -> R?): List<T> {
    return sortedWith(compareByDescending(selector))
}

```

Because `sortedBy` materializes a list immediately, you cannot chain additional sorting criteria to it. To evaluate multiple fields, you must build a composite comparator first, then pass it to `sortedWith`.

## Using compareBy and thenBy with sortedWith for Multi-Field Sorting

The comparator composition API resides in [`libraries/stdlib/src/kotlin/comparisons/Comparisons.kt`](https://github.com/JetBrains/kotlin/blob/main/libraries/stdlib/src/kotlin/comparisons/Comparisons.kt). Lines 89-120 define `compareBy` and `compareByDescending`, while lines 142-172 implement `thenBy` and `thenByDescending` as extension functions on `Comparator`.

### Sorting by Primary and Secondary Keys

To sort by multiple fields, start with `compareBy` for the primary key, then chain `thenBy` for additional ascending criteria:

```kotlin
data class Person(val name: String, val age: Int, val height: Double)

val people = listOf(
    Person("Alice", 30, 165.0),
    Person("Bob", 25, 180.0),
    Person("Alice", 22, 170.0)
)

// Primary: name (ascending), Secondary: age (ascending)
val sorted = people.sortedWith(
    compareBy<Person> { it.name }
        .thenBy { it.age }
)

```

As implemented in [`Comparisons.kt`](https://github.com/JetBrains/kotlin/blob/main/Comparisons.kt), `thenBy` returns a new comparator that first evaluates the original comparator, and only if the result is zero (equal) does it evaluate the subsequent selector.

### Mixing Ascending and Descending Order

To mix sort directions, use `compareByDescending` for the primary field or `thenByDescending` for subsequent fields:

```kotlin
// Primary: name (ascending), Secondary: age (descending), Tertiary: height (ascending)
val mixedSort = people.sortedWith(
    compareBy<Person> { it.firstName }
        .thenByDescending { it.age }
        .thenBy { it.height }
)

```

According to [`Comparisons.kt`](https://github.com/JetBrains/kotlin/blob/main/Comparisons.kt) lines 142-172, `thenByDescending` wraps the selector with `compareByDescending` logic, inverting the natural order of the comparable result.

## Complete Code Examples for Kotlin Multi-Field Sorting

### Sorting Data Classes by Multiple Properties

This example demonstrates the full implementation pattern using `sortedWith` with a composed comparator, referencing the actual stdlib delegation to `Array.sortWith` as found in [`_Collections.kt`](https://github.com/JetBrains/kotlin/blob/main/_Collections.kt) lines 97-104:

```kotlin
data class Employee(
    val department: String,
    val salary: Int,
    val name: String
)

val employees = listOf(
    Employee("Engineering", 90000, "Alice"),
    Employee("Sales", 85000, "Bob"),
    Employee("Engineering", 120000, "Charlie"),
    Employee("Engineering", 90000, "David")
)

// Sort: department asc → salary desc → name asc
val result = employees.sortedWith(
    compareBy<Employee> { it.department }
        .thenByDescending { it.salary }
        .thenBy { it.name }
)

// Result:
// 1. Engineering, 120000, Charlie
// 2. Engineering, 90000, Alice
// 3. Engineering, 90000, David
// 4. Sales, 85000, Bob

```

The `sortedWith` function delegates to a stable sort algorithm (TimSort in the JVM implementation), ensuring that when `salary` values are equal, the original relative order of `Alice` and `David` is preserved, and then `name` is evaluated to break the tie.

### Sorting Map Entries by Key and Value

You can apply the same comparator composition to Map entries, as they are treated as `Iterable` objects:

```kotlin
val scores = mapOf(
    "Team A" to 85,
    "Team C" to 92,
    "Team B" to 85,
    "Team A" to 90
)

// Sort by key ascending, then by value descending
val sorted = scores.entries.sortedWith(
    compareBy<Map.Entry<String, Int>> { it.key }
        .thenByDescending { it.value }
)
    .associate { it.key to it.value }

// Result: {Team A=90, Team B=85, Team C=92}
// Note: Map keys must be unique, so duplicate "Team A" entries would overwrite;
// this example assumes handling of entry pairs before conversion back to Map

```

## Summary

- **`sortedBy`** handles single-field sorting by delegating to `compareBy`, but returns a **List** that cannot be chained for additional criteria.
- **`sortedWith`** accepts a `Comparator<T>`, enabling multi-field sorting when combined with comparator composition functions.
- **`compareBy`** creates the primary comparator; **`thenBy`** and **`thenByDescending`** chain secondary and tertiary criteria as defined in [`libraries/stdlib/src/kotlin/comparisons/Comparisons.kt`](https://github.com/JetBrains/kotlin/blob/main/libraries/stdlib/src/kotlin/comparisons/Comparisons.kt).
- The implementation in [`libraries/stdlib/common/src/generated/_Collections.kt`](https://github.com/JetBrains/kotlin/blob/main/libraries/stdlib/common/src/generated/_Collections.kt) guarantees **stable sorting**, preserving the relative order of elements considered equal by the comparator.

## Frequently Asked Questions

### Can I chain multiple sortedBy calls to sort by multiple fields?

No. Each `sortedBy` call returns a new `List` sorted by a single selector, and subsequent calls re-sort the entire list from scratch, destroying the previous ordering. To sort by multiple fields, use `sortedWith` with a composed comparator built from `compareBy` and `thenBy` chains.

### Is Kotlin's multi-field sorting stable?

Yes. According to the implementation in [`libraries/stdlib/common/src/generated/_Collections.kt`](https://github.com/JetBrains/kotlin/blob/main/libraries/stdlib/common/src/generated/_Collections.kt), both `sortedBy` and `sortedWith` delegate to a stable sorting algorithm (TimSort on the JVM). This means that when two elements are considered equal by your comparator, their original relative order in the source collection is preserved.

### How do I handle null values when sorting by multiple fields?

Use the null-safe variants `compareByDescending` with nullable selectors, or wrap selectors with `nullsFirst()` and `nullsLast()` from the standard library. For example: `compareBy<Person>(nullsFirst(), { it.middleName }).thenBy { it.lastName }`. The [`Comparisons.kt`](https://github.com/JetBrains/kotlin/blob/main/Comparisons.kt) file provides these null-handling comparator extensions.

### What is the performance difference between sortedBy and sortedWith?

There is no significant performance difference in the sorting itself—both use the same underlying stable sort algorithm. However, `sortedBy` creates a temporary comparator internally via `compareBy(selector)`, while `sortedWith` allows you to reuse a composed comparator across multiple collections. For multi-field sorting, `sortedWith` with a pre-built comparator is actually more efficient than chaining multiple `sortedBy` calls, which would create intermediate lists and re-sort data repeatedly.