Kotlin
Recruitment and knowledge question base. Filter, search and test your knowledge.
easykotlinvariablesimmutability
Answer
val declares a read‑only reference you can’t reassign after initialization. var declares a mutable reference that can be reassigned. val doesn’t make the object itself immutable, but it encourages safer code.
easydata-classkotlindto+1
Answer
A data class is a Kotlin class meant to hold data. The compiler generates equals/hashCode, toString, copy() and componentN functions automatically, making it ideal for DTOs and value objects.
easynull-safetynullableelvis-operator+1
Answer
Kotlin types are non‑null by default. To allow null you mark a type with ?. Safe calls (?.), the Elvis operator (?:), and scope functions like let help handle nullable values without NPEs. !! forces a non‑null assertion and can throw.
val name: String? = null
val length = name?.length ?: 0
println(length)mediumcoroutinesconcurrencysuspend+1
Answer
Coroutines are lightweight concurrency primitives for async code. They can suspend without blocking a thread and are scheduled by the Kotlin runtime, so you can run thousands of coroutines on a small thread pool. Threads are OS‑level, heavier and block when waiting.
mediumsealed-classenumwhen+1
Answer
Enums define a fixed set of instances of one type. Sealed classes define a restricted class hierarchy where each subclass can carry different state. Both enable exhaustive when, but sealed classes are more flexible for modeling complex variants.
easyvalvarimmutability
Answer
`val` is a read-only reference (cannot be reassigned), while `var` is mutable (can be reassigned). `val` does not automatically make the object immutable.
easynull-safetyelvisnullable
Answer
`T?` is a nullable type, `?.` is a safe call, `?:` (Elvis) provides a default, and `!!` asserts non-null (throws if it’s null).
val name: String? = null
val len = name?.length ?: 0
// val crash = name!!.length // throws if name is nulleasydata-classdtovalue-object
Answer
It generates `equals/hashCode`, `toString`, `copy`, and `componentN` based on the primary constructor. It’s a good fit for DTO/value-like objects where equality is based on data.
mediumscope-functionsletapply+1
Answer
They mainly differ by the receiver (`this` vs `it`) and the return value (receiver vs lambda result). Example: `apply` configures and returns the object; `let` transforms and returns the lambda result.
val user = User("Ada")
.apply { active = true } // returns receiver
val nameLen = user.name.let { it.length } // returns lambda resultmediumcoroutinesasynclaunch+1
Answer
`launch` starts a coroutine for side-effects and returns a `Job`. `async` returns a `Deferred<T>` for a result and you `await()` it; both should run inside a scope (structured concurrency).
coroutineScope {
val a = async { 1 }
val b = async { 2 }
val sum = a.await() + b.await()
println(sum)
}mediumsealedenumwhen
Answer
Use `sealed` when you need a closed set of variants that can carry different data and types (sum type). It enables exhaustive `when` checks, while `enum` is a fixed set of instances of one type.
sealed interface Result
data class Ok(val value: Int) : Result
data class Err(val message: String) : Result
fun handle(r: Result) = when (r) {
is Ok -> r.value
is Err -> 0
}hardstructured-concurrencycancellationcoroutines
Answer
It means coroutines are tied to a scope, so their lifetime is bounded by the parent job. Cancellation and failures propagate in a controlled way, which prevents “leaking” background work (avoid `GlobalScope` in app code).
hardflowsequencestreams
Answer
`Sequence` is synchronous and lazy on the current thread. `Flow` is a cold asynchronous stream that can suspend, is cancellable, and is collected with coroutines (`collect`), which makes it good for async data streams.
flow {
emit(1)
emit(2)
}.collect { value ->
println(value)
}hardreifiedinlinegenerics
Answer
Because of type erasure, generic type info isn’t available at runtime. `reified` in an `inline` function keeps the type at the call site, so you can do things like `T::class` or `value is T` safely.
inline fun <reified T> Any?.asTypeOrNull(): T? = this as? T
val x: Any = 123
val n: Int? = x.asTypeOrNull<Int>()hardinteropplatform-typesnullability
Answer
A platform type comes from Java where nullability is unknown, so Kotlin treats it as “nullable or non-null” (`String!`). If you treat it as non-null but it’s actually null, you can still get an NPE; prefer proper nullability annotations and safe handling.
easylateinitnull-safetyproperties
Answer
`lateinit` lets you initialize a non-null `var` later (after construction). You can use it only with mutable properties of non-primitive types (not `val`, not `Int`), and you must set it before reading or you’ll get an exception.
mediumcoroutinessuspenddispatcher
Answer
`suspend` means the function can pause without blocking the thread and resume later. It does NOT automatically mean “runs on a new thread” — the dispatcher decides where it runs.
suspend fun loadUser(id: String): User {
// can suspend here (e.g., awaiting IO)
return api.getUser(id)
}mediumextensionsdispatchkotlin-basics
Answer
They let you add functions to a type without modifying its source (syntactic sugar). A common pitfall: extensions are resolved statically by the declared type, so they don’t behave like virtual overrides.
open class Base
class Child : Base()
fun Base.say() = "base"
fun Child.say() = "child"
val x: Base = Child()
println(x.say()) // "base" (static dispatch)hardcoroutinesjobsupervisorjob+1
Answer
With a regular Job, a child failure cancels the parent and usually the whole scope. With SupervisorJob (supervisor scope), a failing child doesn’t cancel siblings; you handle the failure locally.
hardstateflowsharedflowflow+1
Answer
StateFlow always has a current value and replays the latest to new collectors (state). SharedFlow is a more general hot stream: you can configure replay/buffer and use it for events. Use StateFlow for UI state and SharedFlow for one-off events.
easylazydelegatesinitialization
Answer
`by lazy` computes a value on first access and then caches it. It’s useful for expensive initialization you may not need, and it can be thread-safe depending on the chosen mode.
val config by lazy { loadConfig() }
fun loadConfig(): String = "ok"mediuminlineperformancelambdas
Answer
`inline` copies the function body to the call site. It can reduce overhead of higher-order functions by avoiding lambda allocations and virtual calls (but increases bytecode size).
mediumdestructuringdata-classcomponentN
Answer
Destructuring lets you unpack an object into variables (`val (a, b) = obj`). It works via `component1()`, `component2()`, etc., which are generated for data classes (or can be defined manually).
hardvalue-classtype-safetydomain
Answer
A value class wraps a single value to add type-safety without runtime object overhead in many cases (it can be inlined). It’s useful for strong domain types like `UserId` vs `String`.
@JvmInline
value class UserId(val value: String)
fun loadUser(id: UserId) = id.valuehardcoroutinescancellationisActive+1
Answer
Cancellation doesn’t magically stop CPU work; suspending functions check for cancellation, but tight loops must cooperate (check `isActive` or call `yield()`/`ensureActive()`). Otherwise a cancelled coroutine may keep running.
easykotlincollectionsimmutability+1
Answer
`List` is read‑only (no add/remove methods), while `MutableList` allows mutation. Note that a `List` reference can still point to a mutable implementation; the interface just restricts access.
mediumkotlingenericsvariance+1
Answer
`out T` means the type only produces T (you can read T), so it’s covariant (e.g., `List<out Animal>` can hold `List<Dog>`). `in T` means it only consumes T (you can pass T in), so it’s contravariant (e.g., `Comparator<in Dog>`). It prevents unsafe reads/writes.
mediumkotlincoroutinesflow+1
Answer
A `Flow` is usually cold: it starts producing values when you collect it and is great for declarative pipelines. A `Channel` is hot and push-based: it’s like an async queue for events between coroutines. Use Flow for streams/transformations, Channel for communication and fan-in/fan-out.
hardkotlincoroutinesdispatchers+1
Answer
`Dispatchers.Default` is for CPU-bound work. `Dispatchers.IO` is for blocking I/O (DB/files/network clients that block). `Dispatchers.Main` is for UI. The key rule: don’t block `Main` or `Default`; move blocking code to `IO` using `withContext`.
hardkotlincoroutinesexceptions+1
Answer
In structured concurrency, a failing child coroutine typically cancels its parent (unless you use `SupervisorJob`). In `launch`, an uncaught exception goes to the parent/handler; in `async`, the exception is kept until you `await()`. Use `try/catch` for local handling; use `CoroutineExceptionHandler` mainly for top-level `launch` to log/translate crashes.
easykotlinsmart-casttype-safety+1
Answer
Smart cast means the compiler treats a variable as a more specific type after a check like `is` or `!= null`. It works when the value is stable (e.g., a local `val`). It often does not work for mutable `var`, open properties, or values that can change via custom getters or concurrency. Fix: assign to a local `val` or use an explicit cast when safe.
mediumkotlinwhensealed+1
Answer
`when` can return a value (it’s an expression). “Exhaustive” means all possible cases are covered, so you don’t need an `else`. For `enum` and `sealed class`, the compiler can check exhaustiveness and will force you to handle new cases when the type changes.
mediumkotlindelegationby+1
Answer
Interface delegation generates forwarding methods to a delegate object: `class X(private val d: Foo) : Foo by d`. It reduces boilerplate. Limitation: if you want to change behavior, you must explicitly override methods; it’s not “magic inheritance” and doesn’t intercept calls you didn’t override.
interface Logger { fun log(msg: String) }
class ConsoleLogger : Logger {
override fun log(msg: String) = println(msg)
}
class Service(private val logger: Logger) : Logger by logger {
fun work() = log("working")
}hardkotlintailrecrecursion+1
Answer
`tailrec` asks the compiler to optimize tail recursion into a loop (no growing call stack). It works only for direct self-recursion where the recursive call is the last operation (tail position). If the function is not truly tail-recursive, the compiler will not apply the optimization.
hardkotlininlinecrossinline+2
Answer
Inlining copies the lambda body into the call site, so `return` inside the lambda can return from the outer function (non-local return). `crossinline` forbids non-local returns when the lambda might be called later or in another context. `noinline` prevents inlining for a parameter so it can be stored/passed as a value.
mediumkotlinextensiondispatch+1
Answer
Extension functions are statically dispatched based on the compile‑time type, not the runtime type. They don’t actually override member functions. A common pitfall is calling an extension on a variable typed as a base class and expecting the derived extension to run.
easykotlinobjectsingleton+1
Answer
An `object` declaration defines a singleton—one instance created lazily on first access. Use it for stateless utilities or shared services when you truly need a single instance.
easykotlincompaniontop-level+1
Answer
Top‑level functions/properties are simple and don’t require a class. A companion object is tied to a class and is useful for factory methods, constants that conceptually belong to the class, or Java interop (`@JvmStatic`).
mediumkotlinlateinitnull-safety+1
Answer
Use `lateinit var` for a non‑nullable property that will be initialized later (e.g., in DI or lifecycle), when `null` is not a valid state. It works only for mutable, non‑primitive types. You can check initialization with `::prop.isInitialized`.
mediumkotlindelegationproperties+1
Answer
Delegated properties let you reuse getter/setter logic (lazy initialization, observable changes, validation) without boilerplate. You delegate the property to another object that provides `getValue`/`setValue`.
var name: String by Delegates.observable("<init>") { _, old, new ->
println("$old -> $new")
}
name = "Ala"mediumkotlinoperatoroverloading+1
Answer
`operator` lets you map operators (`+`, `[]`, `in`) to functions like `plus`, `get`, `contains`. It can improve readability for domain types, but it can also make code confusing if the operator semantics are surprising.
easykotlintypealiastypes+1
Answer
`typealias` gives a new name for an existing type to improve readability (e.g., long function types). It does NOT create a new distinct type; it’s just an alias.
hardkotlinstateflowsharedflow+1
Answer
StateFlow is a hot stream with a current value (always has one) and replay=1; it’s designed to represent state. SharedFlow is a hot stream for events with configurable replay/buffer; it may have no current value. Use StateFlow for state, SharedFlow for events.
hardkotlincoroutinessupervisor+1
Answer
In `coroutineScope`, a failure in one child cancels the whole scope (and other children). In `supervisorScope`, child failures don’t cancel siblings; only the failing child is cancelled. Use supervisorScope when you want isolation between children.