data class — auto-generates equals / hashCode / toString / copy / componentN
sealed class — closed hierarchy; enables exhaustive when expressions
Scope functions: let/also → it; run/apply/with → this
suspend — pauses without blocking a thread; Dispatchers.IO for I/O, Default for CPU
1 / 5
A Kotlin developer explains: "Kotlin's null safety system forces you to choose between a nullable and non-nullable type at compile time." What do the operators ?, ?., ?:, and !! each do?
Kotlin null safety operators — the full vocabulary:
String? — nullable type declaration Adding ? to a type makes it nullable: val name: String? can hold a String or null. Without ?, the type is non-nullable — the compiler guarantees it is never null.
?. — safe call operator name?.length → returns null if name is null, otherwise evaluates name.length. Replaces the Java null-check pattern. Chainable: user?.address?.city
?: — Elvis operator val len = name?.length ?: 0 → if the left side is null, use the right side as the default. Named after the Elvis hairstyle (?:)
!! — non-null assertion operator name!! → tells the compiler "I guarantee this is not null." If it IS null at runtime, throws NullPointerException. Use only when you have external proof of non-nullability (e.g., a validated variable that the type system cannot track).
Related vocabulary: • NullPointerException (NPE) — the runtime error Kotlin's type system is designed to prevent • smart cast — after a null check, Kotlin automatically casts to the non-nullable type • let — name?.let { println(it) } — run a block only if non-null • lateinit var — declare a non-null var that will be initialised later (before first use)
2 / 5
In a code review, a senior developer comments: "Use a data class here instead of a regular class — you get equals/hashCode/copy/toString for free." What does this mean in Kotlin?
Kotlin data class vocabulary:
Declared with data class User(val id: Int, val name: String)
Auto-generated functions: • equals() — compares by structural equality (property values), not reference • hashCode() — consistent with equals; suitable for use as map keys or in sets • toString() → "User(id=1, name=Alice)" — human-readable output in logs • copy() — create a modified copy: user.copy(name = "Bob") keeps all other fields intact • componentN() — enables destructuring: val (id, name) = user
Restrictions on data classes: • Cannot be abstract, open, or sealed • At least one property in the primary constructor • Properties in primary constructor are used for equals/hashCode; body properties are not
When to use a data class: • DTOs (Data Transfer Objects) — mapping API responses • Value objects in domain-driven design • State representation (e.g., UI state in Android)
Regular class vs data class: A regular class does NOT auto-generate these functions — equals compares by reference. Two objects with identical properties are not equal unless you manually override equals().
Vocabulary: • structural equality (== in Kotlin) — compares property values • referential equality (=== in Kotlin) — compares object identity (same memory address) • destructuring declaration — val (a, b) = pair
3 / 5
An architect says: "Model your domain states as a sealed class — it gives you exhaustive when expressions." What is a sealed class and what does "exhaustive" mean here?
Sealed classes and exhaustive when expressions:
Sealed class definition: A sealed class restricts the type hierarchy — all direct subclasses must be in the same package (Kotlin 1.5+: same package/module). This gives the compiler complete knowledge of all possible subtypes.
sealed class UiState {
object Loading : UiState()
data class Success(val data: List<Item>) : UiState()
data class Error(val message: String) : UiState()
}
Exhaustive when: Because the compiler knows all subclasses, a when expression used as a statement does not require else — it warns (or errors) if you miss a branch. When used as an expression (returning a value), it is required to be exhaustive.
when (state) {
is UiState.Loading -> showSpinner()
is UiState.Success -> showData(state.data)
is UiState.Error -> showError(state.message)
// No else needed — compiler verifies all cases
}
Sealed class vs enum: • enum — each case has the same type; no per-case properties different from others • sealed class — each subclass can have its own properties and methods; much more flexible
Kotlin 1.5+ sealed interface:sealed interface Result — same exhaustiveness, but allows implementing multiple interfaces.
4 / 5
A Kotlin code review mentions: "Use scope functions — let and apply are very different." What is the core difference between let, run, apply, also, and with?
Kotlin scope functions — the complete vocabulary:
All five scope functions run a block on an object but differ on two axes:
Function
Context as
Returns
Primary use
let
it
lambda result
null-safe chain; transform
run
this
lambda result
object config + compute result
apply
this
context object
builder pattern; object init
also
it
context object
side effects (logging)
with
this
lambda result
non-extension; group operations
Most common patterns: • user?.let { sendEmail(it) } — run block only if non-null • TextView(context).apply { text = "Hello"; textSize = 16f } — builder init • list.also { log(it) }.map { transform(it) } — side effect in a chain
Mnemonic: • apply and also return the context object — so they sit in the middle of a chain • let, run, with return the lambda result — use them at the end of a chain or to compute a value
5 / 5
A Kotlin backend PR description says: "Moved the database call to a suspend function running on Dispatchers.IO to avoid blocking the main coroutine." What do these terms mean?
Kotlin coroutines vocabulary:
Coroutine A coroutine is a concurrency primitive that can be suspended (paused) and resumed without blocking a thread. Unlike threads, thousands of coroutines can run on a small number of OS threads.
suspend function A function marked with suspend can be paused at suspension points (e.g., delay(), withContext(), database calls) and resumed later. It can only be called from within another suspend function or a coroutine scope.
suspend fun fetchUser(id: Int): User {
return withContext(Dispatchers.IO) {
database.getUser(id) // blocking call, runs on IO thread pool
}
}
Dispatchers — the coroutine scheduler vocabulary: • Dispatchers.Main — Android UI thread; also used for lightweight suspend operations • Dispatchers.IO — thread pool for blocking I/O (database, network, file system); up to 64 threads (or CPU count, whichever is higher) • Dispatchers.Default — CPU-intensive work (sorting, JSON parsing); thread count = CPU cores • Dispatchers.Unconfined — not confined to any thread; use rarely
launch vs async: • launch { } — fire-and-forget; returns a Job; does not return a value • async { } — returns a Deferred<T>; call .await() to get the result (like Future/Promise)
Structured concurrency: Coroutines follow a parent-child hierarchy via CoroutineScope. When a scope is cancelled, all child coroutines are cancelled too — preventing goroutine-style leaks.