Kotlin Comparable and Comparator

Sorting numbers and strings works out of the box. Sorting your own objects requires telling Kotlin how to compare them. Comparable defines the natural order of a class. Comparator defines a custom or alternative order without changing the class itself.

Comparable — Natural Order

Implement Comparable<T> to give your class a built-in sort order. The compareTo function returns a negative number, zero, or a positive number to indicate less-than, equal, or greater-than.

data class Student(val name: String, val gpa: Double) : Comparable {
    override fun compareTo(other: Student): Int {
        return other.gpa.compareTo(this.gpa)   // descending GPA order
    }
}

fun main() {
    val students = listOf(
        Student("Alice", 3.8),
        Student("Bob",   3.5),
        Student("Carol", 3.9),
        Student("Dan",   3.6)
    )

    val sorted = students.sorted()   // uses compareTo
    sorted.forEach { println("${it.name}: ${it.gpa}") }
}

Output:

Carol: 3.9
Alice: 3.8
Dan: 3.6
Bob: 3.5

compareTo Return Value


compareTo(other) returns:
  negative  → this comes BEFORE other (this < other)
  zero      → this and other are EQUAL
  positive  → this comes AFTER other (this > other)

Example: ascending order
  return this.value.compareTo(other.value)

Example: descending order
  return other.value.compareTo(this.value)

Comparator — Custom Order

A Comparator defines an ordering externally — useful when you need multiple sort orders without modifying the class:

data class Product(val name: String, val price: Double, val rating: Double)

fun main() {
    val products = listOf(
        Product("Laptop",   75000.0, 4.5),
        Product("Mouse",      800.0, 4.8),
        Product("Keyboard",  1500.0, 4.2),
        Product("Monitor",  18000.0, 4.6)
    )

    // Sort by price ascending
    val byPrice = products.sortedWith(compareBy { it.price })
    byPrice.forEach { println("${it.name}: ₹${it.price}") }

    println()

    // Sort by rating descending
    val byRating = products.sortedWith(compareByDescending { it.rating })
    byRating.forEach { println("${it.name}: ★${it.rating}") }
}

Output:

Mouse: ₹800.0
Keyboard: ₹1500.0
Monitor: ₹18000.0
Laptop: ₹75000.0

Mouse: ★4.8
Monitor: ★4.6
Laptop: ★4.5
Keyboard: ★4.2

Multi-Level Sorting

data class Employee(val dept: String, val name: String, val salary: Int)

fun main() {
    val staff = listOf(
        Employee("Tech",  "Alice",  95000),
        Employee("Sales", "Bob",    62000),
        Employee("Tech",  "Carol",  88000),
        Employee("Sales", "Dan",    70000),
        Employee("Tech",  "Eve",    95000)
    )

    // Sort by department first, then by salary descending, then by name
    val sorted = staff.sortedWith(
        compareBy { it.dept }
            .thenByDescending { it.salary }
            .thenBy { it.name }
    )

    sorted.forEach { println("${it.dept.padEnd(6)} | ${it.name.padEnd(6)} | ₹${it.salary}") }
}

Output:

Sales  | Dan    | ₹70000
Sales  | Bob    | ₹62000
Tech   | Alice  | ₹95000
Tech   | Eve    | ₹95000
Tech   | Carol  | ₹88000

Using maxByOrNull / minByOrNull

data class Race(val driver: String, val lapTimeSeconds: Double)

fun main() {
    val results = listOf(
        Race("Hamilton",   83.4),
        Race("Verstappen", 82.1),
        Race("Leclerc",    83.9),
        Race("Russell",    82.8)
    )

    val fastest = results.minByOrNull { it.lapTimeSeconds }
    val slowest = results.maxByOrNull { it.lapTimeSeconds }

    println("Fastest: ${fastest?.driver} (${fastest?.lapTimeSeconds}s)")
    println("Slowest: ${slowest?.driver} (${slowest?.lapTimeSeconds}s)")
}

Output:

Fastest: Verstappen (82.1s)
Slowest: Leclerc (83.9s)

Practical Example: Leaderboard

data class Player(val name: String, val score: Int, val level: Int)

fun main() {
    val players = listOf(
        Player("Alex",  8500, 12),
        Player("Sam",   8500, 15),   // same score, higher level
        Player("Jamie", 9200, 10),
        Player("Taylor",7800, 18)
    )

    val leaderboard = players.sortedWith(
        compareByDescending { it.score }
            .thenByDescending { it.level }
    )

    println("=== LEADERBOARD ===")
    leaderboard.forEachIndexed { i, p ->
        println("${i + 1}. ${p.name.padEnd(8)} Score: ${p.score}  Level: ${p.level}")
    }
}

Output:

=== LEADERBOARD ===
1. Jamie    Score: 9200  Level: 10
2. Sam      Score: 8500  Level: 15
3. Alex     Score: 8500  Level: 12
4. Taylor   Score: 7800  Level: 18

Leave a Comment

Your email address will not be published. Required fields are marked *