iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Exercises

Six Kotlin exercises with self-check answers. Strings, collections, data classes, coroutines, generics.

Kotlin — exercises

EXAMPLE
// ===== Exercise 1: word count =====
// Return a Map<String, Int> of word counts, case-insensitive.

fun wordCount(s: String): Map<String, Int> =
    s.lowercase().split(" ").filter { it.isNotEmpty() }.groupingBy { it }.eachCount()

println(wordCount("the cat sat on the mat"))

// ===== Exercise 2: group anagrams =====
fun groupAnagrams(words: List<String>): List<List<String>> =
    words.groupBy { it.toCharArray().sortedArray().concatToString() }.values.toList()

println(groupAnagrams(listOf("eat", "tea", "tan", "ate", "nat", "bat")))

// ===== Exercise 3: data class with validation =====
data class Money(val cents: Long, val currency: String = "AUD") {
    init {
        require(cents >= 0) { "negative" }
        require(currency.length == 3) { "3-letter currency code" }
    }
    operator fun plus(other: Money): Money {
        require(other.currency == currency) { "currency mismatch" }
        return copy(cents = cents + other.cents)
    }
    override fun toString() = "%.2f %s".format(cents / 100.0, currency)
}

val a = Money(4995)
val b = Money(1000)
println(a + b)   // 59.95 AUD

// ===== Exercise 4: parallel coroutines =====
import kotlinx.coroutines.*

suspend fun fetchOne(id: Int): String {
    delay(100)
    return "User #$id"
}

fun main() = runBlocking {
    val deferreds = (1..5).map { id -> async { fetchOne(id) } }
    val users = deferreds.awaitAll()
    println(users)
}

// ===== Exercise 5: Result-returning safe divide =====
fun divide(a: Int, b: Int): Result<Int> = runCatching {
    require(b != 0) { "divide by zero" }
    a / b
}

println(divide(10, 2))   // Success(5)
println(divide(10, 0))   // Failure(IllegalArgumentException: divide by zero)

// Chain:
val result = divide(10, 2)
    .map { it * 2 }
    .getOrElse { -1 }
println(result)

// ===== Exercise 6: generic Stack =====
class Stack<T> {
    private val items = mutableListOf<T>()
    fun push(x: T) { items.add(x) }
    fun pop(): T? = if (items.isEmpty()) null else items.removeAt(items.lastIndex)
    fun peek(): T? = items.lastOrNull()
    val size get() = items.size
}

val s = Stack<Int>()
s.push(1); s.push(2); s.push(3)
println(s.pop())   // 3
println(s.peek())  // 2

// ===== Patterns to internalise =====
// - groupingBy + eachCount for tallies
// - data class + init for validated value types
// - async + awaitAll for parallel coroutines
// - Result + runCatching for typed error returns
// - Generic classes with reified types where possible

// ===== Pitfalls =====
// - Forgetting Dispatchers.IO on blocking calls inside coroutines
// - Mutating a List<T> (it is read-only; use MutableList<T>)
// - data class equality ignoring custom requirements (init throws instead)
// - GlobalScope.launch instead of structured scope

Why it matters

Six Kotlin exercises drill the daily reflexes: groupingBy + eachCount, data class with init, parallel async, Result with runCatching, generic class. Build them as muscle memory before the next refactor or interview.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// Fill in: fun add(a: Int, b: Int) ____ a + b
Try it Yourself »

Discussion

Loading…