Scope functions, read-only collections, ranges and lazy
Pick between let, apply, also, run and with, know when a list is really mutable, loop over ranges correctly and defer work with lazy.
Kotlin code reviews and screenings keep returning to a few idioms: scope functions, the difference between List and MutableList, range loops and lazy initialisation. Each is small, but using the wrong one makes code harder to read, and interviewers notice.
The five scope functions
All five run a block on an object. They differ in how the object is named inside the block (it or this) and in what they return (the object or the block's result).
class Report(var title: String = "", var pages: Int = 0)
val r = Report().apply { // this; returns the object
title = "Q3"
pages = 12
}
val length = r.title.let { it.length } // it; returns block result -> 2
val input: String? = null
val shown = input?.let { "Got $it" } ?: "nothing" // nothingapply: configure an object, return it.also: side effect such as logging, return the object.let: transform a value, often with?.for null handling, return the result.runandwith: compute a result from an object's members;runworks on nullable receivers with?..
Read-only versus mutable collections
val names = listOf("Ravi", "Asha") // List: no add or remove
val editable = mutableListOf("Ravi")
editable.add("Asha")
println(names.firstOrNull { it.startsWith("Z") }) // null
println((1..5).filter { it % 2 == 1 }.map { it * 10 }) // [10, 30, 50]val stops the variable from being reassigned; it does not make the list read-only. listOf returns a List interface with no mutating methods, which is what you expose from a function. Keep MutableList private and hand out List.
Ranges and lazy
for (i in 1..3) print(i) // 123
for (i in 10 downTo 0 step 5) print("$i ") // 10 5 0
val lazyValue: String by lazy {
println("computing")
"done"
}
println(lazyValue) // computing, then done
println(lazyValue) // done.. includes both ends, until excludes the last value, so 0 until list.size is the safe index loop. by lazy runs the block once, on first access, and caches the result; it is for val. lateinit var is for non-null properties set later by a framework, and reading it before assignment throws.
Common mistakes and a practice habit
- Using
applywhen you need the block's result, orletwhen you only wanted to configure the object. - Writing
0..list.sizeand reading one past the end.
Practice habit: Take a function from your project that builds an object step by step and rewrite it with apply; then take a nullable lookup and rewrite it with ?.let and ?:. Read both versions aloud. If the rewritten one is not clearly shorter and clearer, keep the original. That judgement is the skill being tested.
More in Programming