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.5compareTo 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.2Multi-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 | ₹88000Using 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