How do you build a multi-key Comparator in Kotlin using compareBy, thenBy, and thenByDescending? Show ascending-then-descending ordering.
answer
- compareBy = primary key; thenBy / thenByDescending = tiebreakers
- then* only runs when earlier keys tie
- Apply with sortedWith (copy) or sortWith (in place)
- compareByDescending starts descending
- reversed() flips all keys; thenByDescending flips one
basics
~10 sUse compareBy to pick the first sort key, then chain thenBy for the next key and thenByDescending to flip a key's direction. Pass the result to sortedWith to sort by several fields in order.
solid answer
~40 sKotlin's stdlib gives factory functions that build a Comparator from selector lambdas. compareBy { it.a } creates a comparator ordering by a ascending. You chain refinements with thenBy { it.b } (next tiebreaker, ascending) and thenByDescending { it.c } (descending tiebreaker). Each link only matters when the previous keys tie. You apply it with list.sortedWith(comparator) or sortWith for in-place. compareByDescending starts descending. For custom element comparison you can pass a comparator to a selector, e.g. compareBy(String.CASE_INSENSITIVE_ORDER) { it.name }. Under the hood these use compareValuesBy, which is null-safe and overflow-safe. This composition is preferred over a hand-written compareTo with branchy if-logic because it is declarative, readable, and stable.
code
kotlin · 8 linesdata class Task(val priority: Int, val name: String)
val order = compareByDescending<Task> { it.priority } // high priority first
.thenBy { it.name } // then name A->Z
val tasks = listOf(Task(1, "b"), Task(2, "a"), Task(1, "a"))
println(tasks.sortedWith(order))
// [Task(2,a), Task(1,a), Task(1,b)]go deeper
Can use compareBy to sort by a single field and knows sortedWith applies a comparator.
Chains thenBy/thenByDescending correctly for multi-key order and explains then* only breaks ties.
Distinguishes reversed() vs thenByDescending, leverages stable sort, and passes custom key comparators like CASE_INSENSITIVE_ORDER.
Reasons about comparator composition as reusable, testable ordering policy and its performance/stability guarantees across List and Sequence pipelines.
## The builder functions Kotlin's standard library provides composable `Comparator` factories so you rarely hand-write comparison logic: - `compareBy { selector }` — first/primary key, **ascending** - `compareByDescending { selector }` — first key, **descending** - `comparator.thenBy { selector }` — tiebreaker, ascending - `comparator.thenByDescending { selector }` — tiebreaker, descending - `comparator.thenComparator(other)` — chain an arbitrary Comparator Each `then*` link is consulted **only when all earlier keys compare equal**. This produces a lexicographic, multi-key ordering. ## Ascending-then-descending example Sort people by `lastName` ascending, then by `age` descending (oldest first within a name): ```kotlin data class Person(val lastName: String, val age: Int) val byNameThenAgeDesc = compareBy<Person> { it.lastName } .thenByDescending { it.age } val sorted = people.sortedWith(byNameThenAgeDesc) ``` `compareBy<Person>` needs the explicit type argument when the receiver list type cannot be inferred at that point. ## Applying a comparator - `list.sortedWith(comparator)` -> new sorted `List` (non-mutating) - `mutableList.sortWith(comparator)` -> sorts in place - `sequence.sortedWith(comparator)` -> works on sequences too - `maxWith` / `minWith` -> pick extremes by a Comparator ## Custom element comparison in a key You can give a selector its own Comparator: ```kotlin val ci = compareBy<Person>(String.CASE_INSENSITIVE_ORDER) { it.lastName } ``` Here `String.CASE_INSENSITIVE_ORDER` is a stdlib `Comparator<String>` used to compare the selected key. ## Why prefer this over manual compareTo - **Declarative**: reads like the spec ("by name, then by age desc"). - **Safe**: built on `compareValuesBy`, avoiding overflow from `a - b`. - **Stable**: Kotlin/JVM sorts are stable, so equal-key elements keep input order — important when you only sort by some keys. ## Reversing a whole comparator `comparator.reversed()` flips the entire ordering (every key), distinct from `thenByDescending`, which flips only one key.
- What's the difference between comparator.reversed() and thenByDescending?reversed() inverts the entire ordering (all keys). thenByDescending only makes that one tiebreaker key descending while leaving earlier keys as-is.
- Why does sortedWith keep equal elements in input order?Kotlin/JVM uses a stable sort, so elements that compare equal under the comparator retain their relative original order.
saying these in an interview costs you the question
- Thinking thenBy re-sorts everything instead of acting only on ties
- Using reversed() when they meant to flip just one key
- Calling sort() expecting a return value (it mutates, returns Unit)
- Hand-writing nested if/else compareTo instead of composing builders
- Forgetting the explicit type param compareBy<Person> { ... } when inference fails