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.
Discussion
Loading…