Checkpoint: Idiomatic Kotlin
This checkpoint consolidates the advanced idioms you've learned — annotations, operator overloading, value classes, variance, DSLs, Result, advanced collections, testing, and Java interop — into one build challenge and a self-check quiz.
Learn Checkpoint: Idiomatic Kotlin in our free Kotlin course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick…
Part of the free Kotlin course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
Work the challenge first, then reveal the solution, then test yourself with the quiz before moving on to the capstone.
What This Checkpoint Covers
🔁 Quick Recap
🛠️ Build Challenge: A Typed Receipt
Bring three idioms together: a value class for money, an overloaded + operator, and a collection pipeline that folds a total and groups names by category. Work it yourself first, then reveal the solution.
Notice how the value class keeps Cents distinct from a bare Int , the plus operator makes acc + item.price read naturally, and the two-argument groupBy maps each category straight to a list of names.
⏱ Timed Quiz
📝 Checkpoint Quiz
Answer each in your head, then expand to check. No peeking first!
It must wrap exactly one property, and that property must be a val . On the JVM it also needs the @JvmInline annotation. For multiple fields, use a data class instead.
The function plus backs + , and it must be marked with the operator keyword. A single compareTo (returning an Int ) similarly backs all four of < > <= >= .
MutableList both produces T (reads) and consumes T (adds), so neither in nor out is safe — it must stay invariant. Read-only List only produces, so it's declared out E and a List<Dog> is a List<Animal> .
groupBy returns Map<K, List<V>> , keeping every element that shares a key. associateBy returns Map<K, V> with one value per key, so on a collision the last element wins.
Use runCatching for expected , recoverable failures you want to handle as a value rather than a thrown exception. fold(onSuccess, onFailure) collapses the Result into a single value by handling both branches at once.
@JvmOverloads generates extra overloads so Java callers can omit Kotlin default arguments (Java has no defaults). Java getX()/setX(v) pairs appear in Kotlin as the property x , used with obj.x and obj.x = v .
Practice quiz
What is the one structural restriction on a value class?
- It must have at least two fields
- It cannot have methods
- It must wrap exactly one val property
- It must be abstract
Answer: It must wrap exactly one val property. A value class wraps exactly one val property and, on the JVM, needs the @JvmInline annotation.
Which function name backs the + operator, and what keyword must it carry?
- plus, with the operator keyword
- add, with the override keyword
- combine, with the infix keyword
- sum, with the inline keyword
Answer: plus, with the operator keyword. The function plus backs +, and it must be marked with the operator keyword.
Why is MutableList<T> invariant while List<T> is covariant?
- MutableList only reads T
- List both reads and writes T
- Invariance is just the default and means nothing
- MutableList both produces and consumes T, so neither in nor out is safe
Answer: MutableList both produces and consumes T, so neither in nor out is safe. MutableList both produces (reads) and consumes (adds) T, so it must stay invariant; read-only List only produces, so it is out E and covariant.
What does groupBy return compared to associateBy?
- Both return Map<K, V>
- groupBy returns Map<K, List<V>>; associateBy returns Map<K, V>
- groupBy returns a List; associateBy returns a Set
- Both return Map<K, List<V>>
Answer: groupBy returns Map<K, List<V>>; associateBy returns Map<K, V>. groupBy keeps every element sharing a key as Map<K, List<V>>, while associateBy keeps one value per key (last wins) as Map<K, V>.
When does runCatching beat a try/catch?
- For expected, recoverable failures you want to handle as a value
- Never; try/catch is always better
- Only for syntax errors
- Only inside coroutines
Answer: For expected, recoverable failures you want to handle as a value. runCatching captures success/failure as a Result value, ideal for expected failures you transform with map/fold instead of throwing.
What does fold(onSuccess, onFailure) do to a Result?
- It ignores the failure branch
- It throws if the Result failed
- It collapses the Result into a single value by handling both branches
- It converts Result to a List
Answer: It collapses the Result into a single value by handling both branches. Result.fold collapses both the success and failure branches into one value at once.
What does @JvmOverloads solve?
- It makes a class final
- It generates overloads so Java callers can omit Kotlin default arguments
- It marks a field static
- It renames methods for Java
Answer: It generates overloads so Java callers can omit Kotlin default arguments. Java has no default arguments, so @JvmOverloads generates extra overloads letting Java callers omit them.
How does a Java getX()/setX(v) pair appear when used from Kotlin?
- As getX() and setX() method calls
- It is hidden from Kotlin
- As a function pair x() and x(v)
- As the property x, used with obj.x and obj.x = v
Answer: As the property x, used with obj.x and obj.x = v. Kotlin maps Java getter/setter pairs to a property, accessed as obj.x and assigned with obj.x = v.
What powers a Kotlin DSL builder like apply or buildString?
- A reflection call
- A lambda with receiver, e.g. T.() -> Unit
- An annotation processor
- A sealed class
Answer: A lambda with receiver, e.g. T.() -> Unit. DSL builders use a lambda with receiver (T.() -> Unit) so the lambda body can call the receiver's members directly.
Which variance keyword marks a type parameter that is only produced (returned)?
- in
- reified
- out
- vararg
Answer: out. out marks a covariant producer position; in marks a contravariant consumer position.