Kotlin

Recruitment and knowledge question base. Filter, search and test your knowledge.

Topics
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.

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)

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.

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.

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 null

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.

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 result

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)
}

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).

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)
}

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>()

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.

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)
}

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)

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.

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"

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).

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).

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.value

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.

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.

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.

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.

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`.

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.

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.

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.

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")
}

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.

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.

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.

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.

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`).

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`.

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"

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.

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.

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.

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.