Practice

Kotlin exercises

Make a change, predict what it will do, and compare the result.

  1. 1

    Kotlin Basics

    Use values and string templates.

    Adapt the lesson example. Change one value or element, predict the result, and explain why it changes.

    Show starting example
    Kotlin
    fun main() {
        val name = "Ada"
        println("Hello, $name")
    }
  2. 2

    Kotlin Lists

    Process an ordered collection.

    Adapt the lesson example. Change one value or element, predict the result, and explain why it changes.

    Show starting example
    Kotlin
    fun main() {
        val scores = listOf(82, 91, 76)
        for (score in scores) {
            println(score)
        }
    }
  3. 3

    Kotlin Functions

    Declare typed parameters and a return type.

    Predict average(listOf(1, 2)).

    Reveal answer and reasoning

    The sum is 3, and 3.0 / 2 is 1.5.

    Show starting example
    Kotlin
    fun average(values: List<Int>): Double {
        return values.sum().toDouble() / values.size
    }
    
    fun main() {
        println(average(listOf(82, 91, 76, 95)))
    }
  4. 4

    Kotlin when Expressions

    Choose a result from several conditions with when.

    Add a D grade for scores of 70 or more.

    Reveal answer and reasoning

    Insert score >= 70 -> "D" before else. grade(60) still returns C.

    Show starting example
    Kotlin
    fun grade(score: Int): String = when {
        score >= 90 -> "A"
        score >= 80 -> "B"
        else -> "C"
    }
    
    fun main() {
        println(grade(95))
        println(grade(82))
        println(grade(60))
    }
  5. 5

    Kotlin null safety

    Distinguish nullable from non-null types, and use ?., ?:, let, and !! deliberately.

    What does null?.length return?

    Reveal answer and reasoning

    null. The safe call skips the property access when the receiver is null, and the result type is Int?.

    Show starting example
    Kotlin
    fun describe(name: String?): String {
        val length = name?.length ?: -1
        return name?.let { "name '$it' has $length characters" } ?: "no name"
    }
    
    fun main() {
        println(describe("Ada"))
        println(describe(null))
    
        val items: List<String?> = listOf("a", null, "c")
        println(items.filterNotNull().joinToString("+"))
    
        val text: String? = "kotlin"
        println(text!!.uppercase())
    
        val missing: String? = null
        val result = runCatching { missing!!.length }
        println(result.exceptionOrNull() is NullPointerException)
    }
  6. 6

    Kotlin collections and higher-order functions

    Filter, map, group, sort, and fold lists with lambdas instead of loops.

    How would you list the names sorted by age descending?

    Reveal answer and reasoning

    people.sortedByDescending { it.age }.map { it.name }.

    Show starting example
    Kotlin
    data class Person(val name: String, val age: Int, val city: String)
    
    fun main() {
        val people = listOf(
            Person("Ada", 36, "London"),
            Person("Bo", 25, "Paris"),
            Person("Cy", 41, "London"),
            Person("Di", 25, "Paris"),
        )
    
        println(people.filter { it.age > 30 }.map { it.name })
        println(people.groupBy { it.city }.mapValues { (_, list) -> list.size })
        println(people.sumOf { it.age })
        println(people.sortedWith(compareBy({ it.age }, { it.name })).map { it.name })
        println(people.maxByOrNull { it.age }?.name)
        println(people.partition { it.city == "London" }.let { (london, others) -> london.size to others.size })
        println((1..5).fold(1) { product, n -> product * n })
    }
  7. 7

    Kotlin Classes

    Declare a class with a primary constructor and private state.

    What does account.deposit(-20) do here?

    Reveal answer and reasoning

    Nothing prevents it, so the balance drops to 130. Add a check inside deposit to reject negative amounts.

    Show starting example
    Kotlin
    class Account(val owner: String, private var balance: Int) {
        fun deposit(amount: Int) {
            balance += amount
        }
    
        fun report(): String = "$owner has $balance"
    }
    
    fun main() {
        val account = Account("Ada", 100)
        account.deposit(50)
        println(account.report())
    }
  8. 8

    Kotlin data classes and sealed interfaces

    Model alternatives with a sealed hierarchy and let the compiler check that every case is handled.

    What happens if you add object Triangle : Shape and do not update area?

    Reveal answer and reasoning

    The code stops compiling, because the when expression is no longer exhaustive.

    Show starting example
    Kotlin
    import java.util.Locale
    
    sealed interface Shape
    data class Circle(val radius: Double) : Shape
    data class Rectangle(val width: Double, val height: Double) : Shape
    
    fun area(shape: Shape): Double = when (shape) {
        is Circle -> Math.PI * shape.radius * shape.radius
        is Rectangle -> shape.width * shape.height
    }
    
    fun main() {
        val shapes = listOf(Circle(1.5), Rectangle(2.0, 3.5))
        for (shape in shapes) {
            println(String.format(Locale.ROOT, "%s -> %.2f", shape, area(shape)))
        }
    
        val circle = Circle(2.0)
        val bigger = circle.copy(radius = 3.0)
        println(circle == Circle(2.0))
        println(bigger)
        val (width, height) = Rectangle(4.0, 5.0)
        println(width * height)
    }
  9. 9

    Kotlin object, companion object, and delegation

    Create singletons, class-level factories with private constructors, and reuse behaviour by delegating to an interface.

    How would you test Service without printing?

    Reveal answer and reasoning

    Pass a fake Logger that stores messages in a list, then check the list. That is possible because Service depends on the Logger interface.

    Show starting example
    Kotlin
    interface Logger {
        fun log(message: String)
    }
    
    class ConsoleLogger : Logger {
        override fun log(message: String) = println("LOG: $message")
    }
    
    class Service(logger: Logger) : Logger by logger {
        fun run() {
            log("service started")
        }
    }
    
    class Counter private constructor(val id: Int) {
        companion object {
            private var next = 1
            fun create(): Counter = Counter(next++)
        }
    }
    
    object Config {
        val name = "demo"
        fun describe() = "config $name"
    }
    
    fun main() {
        Service(ConsoleLogger()).run()
        println(Counter.create().id)
        println(Counter.create().id)
        println(Config.describe())
    }
  10. 10

    Kotlin sorting and binary search

    Sort lists with sorted and sortedBy, and find a value or its insertion point with binarySearch.

    How do you sort people by age and then name?

    Reveal answer and reasoning

    people.sortedWith(compareBy({ it.second }, { it.first })), comparing age first and name second.

    Show starting example
    Kotlin
    fun main() {
        val numbers = listOf(5, 2, 9, 1, 5, 6)
        println(numbers.sorted())
        println(numbers.sortedDescending())
    
        val sorted = numbers.sorted()
        println(sorted.binarySearch(9))
        val missing = sorted.binarySearch(4)
        println("4 would be inserted at ${-missing - 1}")
    
        val words = listOf("pear", "apple", "Fig")
        println(words.sorted())
        println(words.sortedBy { it.lowercase() })
    
        val people = listOf("Ada" to 30, "Bo" to 25, "Cy" to 30, "Di" to 25)
        println(people.sortedBy { it.second }.map { it.first })
    }
  11. 11

    Kotlin ArrayDeque as a stack and a queue

    Check balanced brackets with a stack and serve items in arrival order with a queue.

    What does balanced("{[()]}") return?

    Reveal answer and reasoning

    true: each closing bracket matches the most recent unmatched opener.

    Show starting example
    Kotlin
    fun balanced(text: String): Boolean {
        val stack = ArrayDeque<Char>()
        val pairs = mapOf(')' to '(', ']' to '[', '}' to '{')
        for (ch in text) {
            when (ch) {
                '(', '[', '{' -> stack.addLast(ch)
                ')', ']', '}' -> if (stack.removeLastOrNull() != pairs[ch]) return false
            }
        }
        return stack.isEmpty()
    }
    
    fun main() {
        for (test in listOf("(a[b]{c})", "(]", "((", ")(", "")) {
            println("'$test' -> ${balanced(test)}")
        }
    
        val queue = ArrayDeque<String>()
        queue.addLast("Ada")
        queue.addLast("Bo")
        queue.addLast("Cy")
        val served = queue.removeFirst()
        println("$served served, ${queue.size} waiting, next is ${queue.first()}")
    }
  12. 12

    Kotlin maps: counting and indexing

    Count words with groupingBy and build a lookup map with getOrPut.

    How would you find the most frequent word?

    Reveal answer and reasoning

    freq.maxByOrNull { it.value }?.key; with ties, it returns the first one encountered.

    Show starting example
    Kotlin
    fun main() {
        val text = "the cat and the hat and the bat"
        val freq = text.split(" ").groupingBy { it }.eachCount()
        println(freq.entries.sortedWith(compareBy({ -it.value }, { it.key })).joinToString(" ") { "${it.key}=${it.value}" })
    
        val index = mutableMapOf<Char, MutableList<String>>()
        for (word in listOf("apple", "avocado", "banana", "blueberry", "cherry")) {
            index.getOrPut(word[0]) { mutableListOf() }.add(word)
        }
        println(index)
        println(index['b']?.size ?: 0)
        println(index['z']?.size ?: 0)
    }
  13. 13

    Kotlin unit tests with lambdas and runCatching

    Write a minimal test function that reports passes and failures, including expected exceptions.

    How would you add a boundary test for 0?

    Reveal answer and reasoning

    test("accepts the boundary 0") { check(percent("0") == 0) } because 0 is the lowest allowed value.

    Show starting example
    Kotlin
    fun percent(text: String): Int {
        val value = text.toIntOrNull() ?: throw NumberFormatException("not a number: $text")
        require(value in 0..100) { "out of range: $value" }
        return value
    }
    
    var failures = 0
    
    fun test(name: String, body: () -> Unit) {
        val outcome = runCatching(body)
        if (outcome.isSuccess) {
            println("PASS $name")
        } else {
            failures++
            println("FAIL $name: ${outcome.exceptionOrNull()?.message}")
        }
    }
    
    inline fun <reified E : Throwable> assertThrows(code: () -> Unit) {
        val error = runCatching(code).exceptionOrNull()
        check(error is E) { "expected ${E::class.simpleName} but got ${error?.let { it::class.simpleName }}" }
    }
    
    fun main() {
        test("parses 42") { check(percent("42") == 42) }
        test("accepts the boundary 100") { check(percent("100") == 100) }
        test("rejects 101") { assertThrows<IllegalArgumentException> { percent("101") } }
        test("rejects text") { assertThrows<NumberFormatException> { percent("abc") } }
        test("a wrong expectation") { check(percent("7") == 8) }
        println("failures: $failures")
    }
  14. 14

    Kotlin SHA-256 and PBKDF2 on the JVM

    Hash text with MessageDigest and derive a salted password hash with PBKDF2.

    Why store the salt next to the hash?

    Reveal answer and reasoning

    The salt is not secret. Its job is to make each hash unique, and it is needed again to verify the password at login.

    Show starting example
    Kotlin
    import java.security.MessageDigest
    import java.security.SecureRandom
    import javax.crypto.SecretKeyFactory
    import javax.crypto.spec.PBEKeySpec
    
    fun hex(bytes: ByteArray): String = bytes.joinToString("") { "%02x".format(it) }
    
    fun derive(password: String, salt: ByteArray): ByteArray {
        val spec = PBEKeySpec(password.toCharArray(), salt, 600_000, 256)
        return SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(spec).encoded
    }
    
    fun main() {
        val sha = MessageDigest.getInstance("SHA-256")
        println(hex(sha.digest("abc".toByteArray(Charsets.UTF_8))))
        println(hex(sha.digest("abd".toByteArray(Charsets.UTF_8))))
    
        val salt = ByteArray(16).also { SecureRandom().nextBytes(it) }
        val stored = derive("correct horse", salt)
        println("hash length: ${stored.size} bytes")
        println("right password: ${MessageDigest.isEqual(stored, derive("correct horse", salt))}")
        println("wrong password: ${MessageDigest.isEqual(stored, derive("wrong guess", salt))}")
    }
  15. 15

    Kotlin threads, AtomicInteger, and an executor

    Start threads that update a shared counter safely and split a sum across a thread pool.

    What would happen with a plain var counter instead?

    Reveal answer and reasoning

    The final value would usually be less than 200000, and different on every run, because increments from different threads overwrite each other.

    Show starting example
    Kotlin
    import java.util.concurrent.Callable
    import java.util.concurrent.Executors
    import java.util.concurrent.atomic.AtomicInteger
    import kotlin.concurrent.thread
    
    fun main() {
        val counter = AtomicInteger(0)
        val threads = List(4) {
            thread {
                repeat(50_000) { counter.incrementAndGet() }
            }
        }
        threads.forEach { it.join() }
        println("counter = ${counter.get()}")
    
        val pool = Executors.newFixedThreadPool(4)
        val futures = (1..4).map { part ->
            pool.submit(Callable { ((part - 1) * 250 + 1..part * 250).sumOf { it.toLong() } })
        }
        println("sum = ${futures.sumOf { it.get() }}")
        pool.shutdown()
    }