Skip to main content

Access, search, and filter collections

These operations answer three different questions:

  • Access: Which element is at this position?
  • Search: Where is the first element matching this rule?
  • Filter: Give me every element matching this rule.

Safe element access​

NeedThrowing operationSafer nullable operation
First elementfirst()firstOrNull()
Last elementlast()lastOrNull()
Exactly one elementsingle()singleOrNull()
Element at indexlist[index] / elementAt()getOrNull() / elementAtOrNull()
fun main() {
val names = listOf("Rakesh", "Anu")
val empty = emptyList<String>()

println(names.first()) // Rakesh
println(names.getOrNull(10)) // null
println(empty.firstOrNull()) // null
println(empty.singleOrNull()) // null

val failure = runCatching { empty.first() }
println(failure.exceptionOrNull()!!::class.simpleName)
}
Open in Kotlin Playground

Use a throwing operation when absence means a broken invariant. Use the nullable form when absence is expected business behavior. Do not automatically replace every exception with null; that can hide corrupted state.

Search by value, position, or predicate​

fun main() {
val skills = listOf("Java", "Kotlin", "Compose", "Kotlin")

println(skills.indexOf("Kotlin")) // 1
println(skills.lastIndexOf("Kotlin")) // 3
println(skills.indexOfFirst { it.length > 6 }) // 2
println(skills.indexOfLast { it.startsWith("K") }) // 3
println(skills.find { it.startsWith("C") }) // Compose
println(skills.find { it.startsWith("X") }) // null
}
Open in Kotlin Playground

find(predicate) is an alias for firstOrNull(predicate). Index-search operations return -1 when nothing matches; element-search operations commonly return null.

Fallback and arbitrary retrieval​

fun main() {
val values = listOf("A", "B", "C")

println(values.elementAtOrElse(10) { index -> "fallback-for-$index" })
println(values.random())
println(values.randomOrNull())
println(emptyList<String>().randomOrNull()) // null
}
Open in Kotlin Playground

random() throws on empty input; randomOrNull() returns null. Both are nondeterministic for a non-empty input unless supplied a controlled Random. Inject a seeded Random in repeatable tests. Do not use ordinary pseudo-random collection selection for security-sensitive choices.

Filter families​

OperatorWhat remains
filterMatching values
filterNotNon-matching values
filterIndexedValues selected using index and value
filterNotNullNon-null values, with nullable type removed
filterIsInstance<T>Runtime values of type T
fun main() {
val values: List<Any?> = listOf(10, 20L, 30.5f, "Android", null, 40)

val ints = values.filterIsInstance<Int>()
val numbers = values.filterIsInstance<Number>()
val total = numbers.sumOf { it.toDouble() }

println(ints) // [10, 40]
println(numbers) // [10, 20, 30.5, 40]
println(total) // 100.5
}
Open in Kotlin Playground
Interview trap: Int is not every Number

filterIsInstance<Int>() keeps only Int. It excludes Long, Float, and Double. Use Number when the domain genuinely accepts different numeric representations, then convert deliberately before arithmetic.

Predict before revealing​

val words = listOf("one", "three", "two")
println(words.first { it.length == 3 })
println(words.filter { it.length == 3 })
Reveal the output and explanation

first stops at one. filter examines the complete list and returns [one, two].

Open in Kotlin Playground

Android scenario​

When reading a selected item from UI state, firstOrNull may correctly represent “nothing selected.” When reading a required database migration record, returning null may hide a violated invariant. The operation is a product decision, not merely a syntax preference.

Interview questions​

  1. Why can firstOrNull() be safer but still logically wrong?
  2. What does indexOfFirst return when no element matches?
  3. Why does filterIsInstance<Int>() exclude a Long?
  4. When should single() be preferred over first()?