Start learning free

LESSON 267 OF 677 · PAIRS, SORTING & BUILT-INS

Custom Sorting in Kotlin: sortedBy and compareBy

Lesson Read the full explanation

TL ; DR:

To sort by your own rule, pass a lambda. It is a small function in braces. It says what to sort each item by.


val nums = mutableListOf(25, 13, 5, 41, 33)

val byLastDigit = nums.sortedWith(compareBy({ it % 10 }, { it }))
// [41, 13, 33, 5, 25]

val scores = mutableListOf(70, 95, 82)

val highFirst = scores.sortedByDescending { it }
// [95, 82, 70]


Full lesson:

You met sorted() in the Shopping Cart project. It sorts from small to large, or from A to Z. Sometimes you need another order. The shortest word may have to come first, or the highest score.

Then pass a lambda. A lambda is a small function in braces. It has no name. In { it.length }, it is the one item the lambda gets. The lambda returns that item's length.

  • The call sortedBy { it.length } sorts by what the lambda returns, from small to large.
  • The call sortedByDescending { it } sorts from large to small.
  • The call sortedWith(compareBy({ it % 10 }, { it })) uses two rules. It sorts numbers by their last digit, then from small to large. The second lambda only sorts items that tie on the first.

The sorted... functions make a new list. The old list stays as it was.

On a mutableListOf list, sortBy and sortWith sort the list itself instead.


Your task

A pet shop sells toys. Their prices are in prices. Make a list named highFirst with the prices from high to low.

The shop's pets are in names. Make a list named byLastLetter with the names sorted by their last letter, in alphabetical order. Names with the same last letter go from A to Z.

The code already prints both, one item per line.

Expected output:

30
12
8
5
Bella
Luna
Bo
Coco
Max
Rex
STEP 1 OF 2
    Ctrl + Enter to check

    KOTLIN · PAIRS, SORTING & BUILT-INS

    Custom Sorting in Kotlin, explained

    Why custom sorting

    sorted() puts numbers from small to large and strings from A to Z. Often you need another order: the highest price first, the shortest word first, or names ordered by their last letter. For that, pass a lambda: a small function in braces with no name. It says what to sort each item by. In { it.length }, it is the one item the lambda gets, and the lambda returns that item's length.

    The sorting functions

    • sortedBy { it.length } sorts by what the lambda returns, from small to large.
    • sortedByDescending { it } sorts from large to small.
    • sortedWith(compareBy({ it % 10 }, { it })) uses two rules. The second lambda only orders items that tie on the first.
    val scores = mutableListOf(70, 95, 82)
    val highFirst = scores.sortedByDescending { it }
    
    val nums = mutableListOf(25, 13, 5, 41, 33)
    val byLastDigit = nums.sortedWith(compareBy({ it % 10 }, { it }))

    Printing the lists gives [95, 82, 70] and [41, 13, 33, 5, 25]. The first rule sorts by last digit; numbers with the same last digit (13 and 33, or 5 and 25) then go from small to large.

    To send ties from large to small instead, build the comparison with compareBy<Int> { it % 10 }.thenByDescending { it }. Used with sortedWith on the same numbers, it gives [41, 33, 13, 25, 5], so 33 now comes before 13.

    Things to know

    • The sorted... functions make a new list and leave the old one as it was. With words holding [pear, fig, apple], words.sortedBy { it.length } gives [fig, pear, apple] while words still prints [pear, fig, apple].
    • On a mutableListOf list, sortBy and sortWith sort the list itself instead. After words.sortBy { it.length }, words prints [fig, pear, apple].
    • Don't chain sortedBy { it % 10 }.sortedBy { it } to get two rules: the second call sorts everything again and forgets the first. On the numbers above it gives [5, 13, 25, 33, 41]. Use compareBy with both lambdas.

    The lesson's task

    A pet shop has toy prices 12, 5, 30 and 8, and pets Rex, Luna, Bella, Max, Bo and Coco. Make highFirst with the prices from high to low, and byLastLetter with the names sorted by their last letter, with names that share a last letter going from A to Z.

    1. Call sortedByDescending on prices with { it }: each price is sorted by itself, from the biggest down.
    2. For the names, call sortedWith with compareBy and two lambdas. The first gives the last letter, { it[it.length - 1] }. A string indexed with a number gives a Char, and chars sort in alphabetical order.
    3. The second lambda is { it }, so names with the same last letter go from A to Z. Max and Rex both end in x, and this puts Max first.
    class Solution {
        fun mainFunction() {
            val prices = mutableListOf(12, 5, 30, 8)
            val names = mutableListOf("Rex", "Luna", "Bella", "Max", "Bo", "Coco")
    
            val highFirst = prices.sortedByDescending { it }
            for (price in highFirst) {
                println(price)
            }
    
            val byLastLetter = names.sortedWith(compareBy({ it[it.length - 1] }, { it }))
            for (name in byLastLetter) {
                println(name)
            }
        }
    }

    The prices print as 30, 12, 8, 5. The names end in a (Bella, Luna), o (Bo, Coco) and x (Max, Rex), so they print as:

    Bella
    Luna
    Bo
    Coco
    Max
    Rex

    Common mistakes

    • Expecting the original list to change. sortedBy and sortedWith return a new list; print that one, not prices or names.
    • Chaining two sortedBy calls for two rules. Only the last one counts.
    • Mixing up the direction. sortedBy goes small to large; sortedByDescending goes large to small.

    Key takeaways

    • A lambda tells sortedBy and sortedByDescending what to sort each item by.
    • sortedWith(compareBy(...)) takes several lambdas; later ones only matter on ties.
    • sorted... returns a new list, while sortBy and sortWith change a mutable list in place.
    Keep going with Kotlin500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free