skip to content

How do you handle nulls and reverse natural ordering when building Comparators in Kotlin (nullsFirst, nullsLast, reverseOrder, naturalOrder)?

level: middleimportance: should knowfreq 55%

answer

  1. naturalOrder() ascending, reverseOrder() descending primitives
  2. nullsFirst / nullsLast control where nulls land
  3. Wrapping order: nullsLast handles nulls, inner cmp handles the rest
  4. reversed() flips a built comparator; reverseOrder() is descending natural
  5. Unwrapped null comparison -> NPE inside compareTo

basics

~10 s

Use naturalOrder() for the type's default order and reverseOrder() to flip it. Wrap a comparator with nullsFirst() or nullsLast() so nullable values sort to the front or back instead of crashing.

solid answer

~40 s

Kotlin stdlib provides naturalOrder<T>() and reverseOrder<T>() that return Comparators for Comparable types, plus nullsFirst(cmp) and nullsLast(cmp) that wrap a Comparator<T> into a Comparator<T?> placing nulls before or after non-null values. Without a null-aware wrapper, comparing a null with compareBy on a nullable selector would throw an NPE inside the underlying compareTo. You combine them: e.g. compareBy(nullsLast(naturalOrder())) { it.middleName } puts records with no middle name last. selector-based helpers like sortedBy already null-handle the *selector returning null* via compareValuesBy, but explicit nullsFirst/nullsLast give you control over placement. reversed() on an existing comparator and reverseOrder() differ: reversed() flips a built comparator; reverseOrder() is the descending natural order primitive.

code

kotlin · 7 lines
kotlin
val data = listOf("b", null, "a", null, "c")

println(data.sortedWith(nullsFirst(naturalOrder())))
// [null, null, a, b, c]

println(data.sortedWith(nullsLast(reverseOrder())))
// [c, b, a, null, null]

go deeper

for a junior

Knows nullsFirst/nullsLast exist to avoid crashes when sorting nullable values.

for a middle

Correctly composes nullsLast(naturalOrder()) in a selector and explains reverseOrder vs reversed.

for a senior

Reasons about wrapping order (null placement vs inner order independence) and selector-null vs element-null cases.

for a principal

Defines null-ordering as an explicit, documented policy across an API so callers never hit ambiguous or NPE-prone sorts.

## The primitives - `naturalOrder<T>()` — `Comparator<T>` using `T`'s `Comparable` natural ordering (ascending). - `reverseOrder<T>()` — the **descending** natural ordering (equivalent to `naturalOrder<T>().reversed()`). - `comparator.reversed()` — flips any existing comparator. These require `T : Comparable<T>`. ## Null handling A `Comparator<T>` can't compare a `null` on its own — invoking `compareTo` on null throws `NullPointerException`. The stdlib offers wrappers that lift a `Comparator<T>` into a `Comparator<T?>`: - `nullsFirst(comparator)` — nulls sort **before** all non-null values. - `nullsLast(comparator)` — nulls sort **after** all non-null values. - `nullsFirst()` / `nullsLast()` (no arg) — use natural order for the non-null comparison. ```kotlin data class User(val name: String, val nickname: String?) val byNickname = compareBy(nullsLast<String>()) { u: User -> u.nickname } users.sortedWith(byNickname) // users with null nickname go last ``` Here `nullsLast<String>()` builds a `Comparator<String?>` using natural String order, then `compareBy` adapts it to `User` via the selector. ## Reversing — three distinct tools ```kotlin val asc = compareBy<Int> { it } // 1,2,3 val desc1 = asc.reversed() // 3,2,1 (flip built comparator) val desc2 = compareByDescending<Int> { it }// 3,2,1 (selector-level descending) val desc3 = reverseOrder<Int>() // 3,2,1 (natural descending primitive) ``` All three give descending here, but they compose differently in multi-key comparators: `reversed()` flips *every* key, `compareByDescending`/`thenByDescending` flip *one* key. ## Interaction with nulls + reverse Order of wrapping matters. `nullsLast(reverseOrder())` reverses the non-null elements but **still** puts nulls last, because `nullsLast` controls null placement independently of the inner order: ```kotlin listOf(3, null, 1, 2).sortedWith(nullsLast(reverseOrder())) // [3, 2, 1, null] ``` ## Selector returning null vs element being null `sortedBy { it.field }` where `field` is nullable will throw if the selected value is null and you don't wrap it. Use `sortedWith(compareBy(nullsLast(naturalOrder())) { it.field })` for safety.

  • What happens if you sortedBy a nullable selector without a null wrapper and a value is null?
    It throws NullPointerException when the underlying compareTo is invoked on null. Wrap with nullsFirst/nullsLast.
  • Does nullsLast(reverseOrder()) put nulls first or last?
    Last. nullsLast always places nulls after non-nulls; reverseOrder only affects the relative order of the non-null values.

saying these in an interview costs you the question

  • Assuming Kotlin silently treats null as the smallest value by default
  • Believing reverseOrder() also moves nulls
  • Wrapping in the wrong order and expecting different null placement
  • Not realizing an unwrapped null selector throws NPE
  • Confusing reversed() (flip built cmp) with reverseOrder() (descending primitive)

context