Lessons
Sealed classes and exhaustive when
Sealed class hierarchy, sealed interfaces (Kotlin 1.5+), exhaustive when expressions, and modeling state with sealed classes.
What to look for:
- when on a sealed type is exhaustive — compiler warns if a case is missing (like Rust enum match)
- Sealed subclasses can be data classes, regular classes, or objects
- Use sealed class for Result<T>, NetworkState, UiEvent — typed, exhaustive outcomes
- Sealed interface allows a class to implement multiple sealed hierarchies
Scope functions: let, run, apply, also, with
When to use each scope function, null-safe let, builder-pattern apply, side-effect also, and the this vs it distinction.
Prerequisite: Foundations Track
What to look for:
- apply: configure an object, returns the object — perfect for builder patterns
- let: transform a value, returns lambda result — most useful for null-safe operations
- also: side effect (logging, debugging), returns the object unchanged
- Don't overuse scope functions — readability matters; explicit is sometimes clearer
Generics: type parameters and variance
Generic classes and functions, type constraints (where T : Comparable<T>), covariance (out), contravariance (in), and star projection (*).
Prerequisite: Lesson 1
What to look for:
- out T: covariant — the class only produces T (like List<out T>); List<String> is a List<Any>
- in T: contravariant — the class only consumes T; Comparator<Any> is a Comparator<String>
- reified: in inline functions, reified T lets you use T::class at runtime (no type erasure)
- Star projection List<*>: unknown type — can read as Any?, cannot write
Interfaces, abstract classes, and delegation
Interface with default implementations, abstract classes, by delegation (class A(val b: B) : Interface by b), and lazy delegation.
Prerequisite: Lesson 2
What to look for:
- Kotlin interfaces can have default implementations — like Java 8 default methods
- by delegation: class Logger(impl: LogService) : LogService by impl — delegates to impl automatically
- by lazy: val expensive by lazy { computeExpensiveThing() } — computed once on first access
- by Delegates.observable: fires a callback when value changes — useful for reactive patterns
Object expressions, companion objects, and inline classes
Anonymous objects, object expressions, companion object (static-like), @JvmStatic, and value classes (inline classes) for zero-cost wrappers.
Prerequisite: Lessons 1–4
What to look for:
- companion object is not static — it's an object instance; @JvmStatic makes it callable as static from Java
- object expression: val listener = object : ClickListener { override fun onClick() { ... } }
- @JvmInline value class UserId(val id: String) — wraps String with no runtime overhead
- Value classes help distinguish UserId from OrderId even though both wrap String
How to use this track: Each lesson links to the Kotlin reference pages. For sealed class patterns, the Kotlin documentation's "Sealed classes and interfaces" page is the authoritative reference. Practice at play.kotlinlang.org.
Track quiz
4 questions to check your understanding.
Why is when exhaustive on a sealed class but not a regular class?
Which scope function returns the receiver object (not the lambda result)?
What does out T mean in a Kotlin generic class?
What does val result by lazy { compute() } do?