Test Yourself!

Each of these ten questions is a complete program that ran with kotlinc 2.4.20 on JDK 17 (Kotlin Playground), with kotlinx.coroutines 13,816 or kotlinx.serialization 1.11.0 5,947 on the classpath where it imports them; treat each numbered block as a separate file. Write down what it prints and why, then check Appendix H, which gives the real output, the reason and the section it comes from.

Questions

Questions 1-4: nulls, data classes, extensions and scope functionsKotlin
// 1. What do the safe calls print for a missing note? (Sections 5.2.2 and 5.2.3)
fun main() {
  val note: String? = null
  val words = note?.split(" ")?.size ?: 0
  println("$words ${note?.length} $note ${note.isNullOrBlank()}")
  println(try { note!!.length } catch (e: NullPointerException) { e::class.simpleName })
}
// 2. Which properties do equals(), copy() and toString() see? (Sections 5.4.3 and 5.4.4)
data class Book(val id: Int, val title: String) { var notes = 0 }
fun main() {
  val a = Book(1, "The Quiet Harbor").apply { notes = 3 }; val b = a.copy()
  println("${a == b} ${a === b} ${b.notes} ${setOf(a, b).size} $b")
}
// 3. A member and an extension with the same name: which one runs? (Section 5.6.3)
open class Shelf { fun label() = "member" }; class Wishlist : Shelf()
fun Shelf.label() = "Shelf extension"
fun Wishlist.label() = "Wishlist extension"
fun main() {
  val w = Wishlist(); val s: Shelf = w
  println("${w.label()} ${s.label()}")
}
// 4. What does each scope function return? (Section 5.7.3)
fun main() {
  val a = StringBuilder("Book").apply { append("Nest") }.length
  val b = "BookNest".let { it.length > 5 }
  val c = mutableListOf(1).also { it += 2 }.size
  val d = 21.6.takeIf { it < 20.0 } ?: "full price"
  println("$a $b $c $d")
}
Questions 5-7: sequences, cancellation and StateFlowKotlin
// 5. In what order do the steps run, and when? (Section 5.7.4)
val prices = listOf(14.99, 39.50, 24.00, 18.75, 16.20, 21.30)
fun main() {
  val s = prices.asSequence().map { print("m "); it * 2 }.filter { print("f "); it > 40 }
  println("built"); println(s.first())
  println(prices.map { it * 2 }.count { it > 40 })
}
// 6. A catch-all around a cancelled coroutine. What runs? (Section 5.8.5)
import kotlinx.coroutines.*
fun main() = runBlocking {
  val sync = launch {
    try { repeat(5) { println("page $it"); delay(100) } }
    catch (e: Exception) { println("caught ${e::class.simpleName}") }
    finally { println("finally") }
  }
  delay(150); sync.cancelAndJoin(); println("cancelled=${sync.isCancelled}")
}
// 7. Which cart sizes does the collector see? (Sections 5.8.7 and 5.23.3)
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
data class Cart(val ids: List<Int>)
fun main() = runBlocking {
  val cart = MutableStateFlow(Cart(emptyList())); val seen = mutableListOf<Int>()
  val job = launch { cart.collect { seen += it.ids.size } }
  delay(50); cart.value = Cart(listOf(3)); cart.value = Cart(listOf(3)); delay(50)
  cart.value = Cart(listOf(3, 5)); cart.value = Cart(listOf(3)); delay(50)
  job.cancel(); println(seen)
}
Questions 8-10: JSON, lookups and version codesKotlin
// 8. What do a lenient and a strict Json make of the server's reply? (Section 5.27.5)
import kotlinx.serialization.*
import kotlinx.serialization.json.Json
@Serializable data class BookDto(val id: Int, val title: String,
  @SerialName("cover_url") val coverUrl: String? = null, val inStock: Boolean = true)
fun main() {
  val body = """{"id":3,"title":"Salt and Saffron","updatedAt":"2026-09-25"}"""
  println(Json { ignoreUnknownKeys = true }.decodeFromString<BookDto>(body))
  val strict = runCatching { Json.decodeFromString<BookDto>(body) }
  println(strict.exceptionOrNull()!!::class.simpleName)
}
// 9. Four ways to look a book up. (Sections 5.7.6, 5.21.3 and 5.35.5)
data class Book(val id: Int, val title: String)
val books = listOf(Book(1, "The Quiet Harbor"), Book(2, "Patterns of the Deep Web"))
fun main() {
  println(books.firstOrNull { it.id == 99 }?.title ?: "No book with id 99")
  println(books.find { it.id == 2 }?.title)
  try { books.first { it.id == 99 } } catch (e: NoSuchElementException) { println(e.message) }
  println(runCatching { books.single() }.exceptionOrNull()?.message)
}
// 10. BookNest's versionCode rule and price formatting. (Sections 5.38.1 and 5.39.5)
fun versionCode(name: String) = name.split(".").map(String::toInt)
  .let { (major, minor, patch) -> major * 10000 + minor * 100 + patch }
fun formatPrice(amount: Double): String {
  val cents = kotlin.math.round(amount * 100).toLong()
  return "$" + (cents / 100) + "." + (cents % 100).toString().padStart(2, '0')
}
fun main() {
  println(listOf("1.0.0", "1.2.10", "1.2.100", "1.3.0").map(::versionCode))
  println("${0.1 + 0.2} ${formatPrice(0.1 + 0.2)} ${formatPrice(14.995)} ${24.0}")
}