82 Kotlin Interview Questions and Answers (2026)

Blog / 82 Kotlin Interview Questions and Answers (2026)
Kotlin interview questions and answers

Kotlin started as the Android default and never stopped growing—it now runs serious server-side workloads too (Ktor, Spring Boot, and coroutine-powered backends), so more teams expect real fluency, not just Java-with-fewer-semicolons. Walk in shaky on coroutines or null safety and you will lose the offer to someone who is not.

This is your fix: 82 questions with concise, interview-ready answers and code where it helps. They're worked Junior → Mid → Senior, so you build from the type system and constructors up to structured concurrency, flows, and the compiler internals. Work through them and you'll have an answer ready for whatever they throw.

Q1.
What is a Data Class? What methods does the compiler generate and what are the requirements for its primary constructor?

Junior

A data class is a class whose main purpose is to hold data; the compiler auto-generates the boilerplate equality, hashing, string, copy, and destructuring methods based on the properties in the primary constructor.

  • Generated methods (derived from primary-constructor properties only):

    • equals() and hashCode(): structural equality based on the declared properties.

    • toString(): readable form like User(name=Ann, age=30).

    • componentN() functions: enable destructuring (val (a, b) = user).

    • copy(): create a modified clone with named-argument overrides.

  • Primary-constructor requirements:

    • Must have at least one parameter.

    • All parameters must be marked val or var.

    • Cannot be abstract, open, sealed, or inner.

  • Key caveat: properties declared in the class body (not the primary constructor) are ignored by the generated equals(), hashCode(), and toString().

Q2.
What is the difference between an object declaration and a companion object?

Junior

An object declaration defines a standalone singleton, while a companion object is a singleton tied to an enclosing class so its members can be accessed through the class name.

  • Object declaration:

    • A thread-safe, lazily initialized singleton created on first access.

    • Independent: not bound to any other class; accessed by its own name (Logger.log()).

    • Can implement interfaces and extend classes.

  • Companion object:

    • A single object scoped inside a class; only one per class.

    • Used to hold factory methods and class-level constants, replacing Java static members.

    • Accessed via the class name: MyClass.create() calls into MyClass.Companion.

    • Can be named, and can access the enclosing class's private members.

Q3.
What is a companion object and how does it differ from a static member in Java?

Junior

A companion object is a singleton object declared inside a class that holds members accessible through the class name; it serves the role of Java static members but is a real object instance, not a static namespace.

  • How it behaves like Java static:

    • Called via the class: Factory.create() without an instance.

    • Holds constants and factory functions.

  • How it differs from Java static:

    • It is a genuine object instance, so it can implement interfaces and be passed as an argument.

    • It can have a name and use extension functions.

    • Kotlin has no static keyword at all; the companion is the idiomatic replacement.

    • For true static interop (e.g. calling from Java without .Companion), annotate members with @JvmStatic.

kotlin

class User private constructor(val name: String) { companion object { fun create(name: String) = User(name) } } val u = User.create("Ann")

Q4.
What features does an enum class provide in Kotlin, such as entries, valueOf, and associating properties or methods with constants?

Junior

A Kotlin enum class is a type-safe set of constants that can carry properties, define methods, override behavior per constant, and exposes built-in members for listing and lookup.

  • Built-in members:

    • entries: the modern (Kotlin 1.9+) immutable list of all constants, replacing values().

    • valueOf(name): looks up a constant by name, throwing if not found.

    • name and ordinal: the declared name and zero-based position of each constant.

  • Associating data and behavior:

    • Constants can take constructor arguments to hold properties.

    • You can define methods, and even abstract methods overridden per constant.

    • Enums work exhaustively in when expressions.

kotlin

enum class Planet(val mass: Double) { EARTH(5.97e24) { override fun greet() = "home" }, MARS(6.42e23) { override fun greet() = "red" }; abstract fun greet(): String } Planet.entries // all constants Planet.valueOf("MARS") // lookup by name

Q5.
Explain the difference between == (structural equality) and === (referential equality) in Kotlin.

Junior

In Kotlin == checks structural equality (are the values equal?) by calling equals(), while === checks referential equality (are they the exact same object in memory?).

  • == (structural):

    • Translates to a?.equals(b) ?: (b === null), so it is null-safe and never throws an NPE.

    • Override equals() to define what "equal" means; data classes generate it automatically.

  • === (referential):

    • True only when both operands point to the same instance; cannot be overridden.

    • Negations are != and !==.

kotlin

val a = listOf(1, 2) val b = listOf(1, 2) println(a == b) // true (same contents) println(a === b) // false (different objects)

Q6.
What is the difference between val and var, and does val guarantee true immutability?

Junior

val declares a read-only reference (assigned once), and var declares a reassignable one. val guarantees the reference cannot be reassigned, but it does not guarantee the object it points to is immutable.

  • val (read-only reference):

    • Can be initialized only once; reassignment is a compile error.

    • Can be backed by a custom getter, so its value may still vary between reads.

  • var (mutable reference): Can be reassigned freely to a new value of the same type.

  • Why val is not deep immutability:

    • It freezes the reference, not the referenced object's internal state.

    • A val pointing to a MutableList can still have elements added; you just can't point it at a different list.

    • For true immutability you need immutable types (read-only interfaces, data class with val fields holding immutable values).

kotlin

val list = mutableListOf(1, 2) list.add(3) // OK: object's state changed // list = mutableListOf() // compile error: reassignment

Q7.
How does type inference work in Kotlin, and when must you specify types explicitly?

Junior

Kotlin's compiler infers a declaration's type from the initializing expression or return value, so you often omit the type annotation; you must supply it explicitly when there is no expression to infer from or when you want a type other than what would be inferred.

  • How inference works:

    • The static type comes from the right-hand side: val x = 5 infers Int.

    • Single-expression function bodies infer the return type; lambda parameter and return types are inferred from the expected functional type.

    • It is fully static, not dynamic: the type is fixed at compile time and cannot change later.

  • When you must be explicit:

    • Declaring without initializing: val name: String (nothing to infer from).

    • Public API signatures: function parameters and return types of regular (block-body) functions are required.

    • When you want a broader or different type than inferred (e.g. val n: Number = 5 or val x: Long = 5).

    • To make a property nullable: var s: String? = null.

  • Style guidance: Rely on inference for locals to reduce noise; annotate public/exported signatures explicitly for clarity and API stability.

Q8.
Explain the difference between the Safe Call operator (?.), the Elvis operator (?:), and the Not-null assertion operator (!!). When is it acceptable to use !!?

Junior

All three deal with nullable values but differ in outcome: ?. calls only if non-null (else yields null), ?: provides a fallback when the left side is null, and !! forces a non-null read and throws an NPE if it's null.

  • Safe call ?.: a?.b returns a.b if a is non-null, otherwise null; chainable and short-circuiting.

  • Elvis ?: a ?: b evaluates to a if non-null, otherwise b; often paired with ?. to supply a default or return/throw.

  • Not-null assertion !!: a!! returns a if non-null, else throws NullPointerException; it bypasses null safety.

  • When !! is acceptable:

    • When you can prove non-null but the compiler can't (e.g. value set in a lifecycle callback), and a null genuinely indicates a bug you'd want to fail fast on.

    • Avoid it as a convenience to silence the compiler: prefer ?., ?:, or restructuring.

kotlin

val len = name?.length ?: 0 // safe call + elvis default val city = user?.address?.city ?: "Unknown" val id = config!!.id // throws if config is null

Q9.
Explain the Elvis Operator (?:) and the Safe Call Operator (?.). How do they help eliminate NullPointerExceptions?

Junior

The safe call ?. performs a member access only when the receiver is non-null, returning null otherwise; the Elvis operator ?: supplies a fallback value when its left side is null. Together they let you express null handling explicitly instead of risking an unchecked dereference.

  • Safe call ?.:

    • Short-circuits the whole chain to null the moment any link is null, so you never dereference null.

    • Combine with let to run a block only when non-null: x?.let { ... }.

  • Elvis ?: Converts a nullable result into a guaranteed non-null one via a default, or exits early with return/throw.

  • How they prevent NPEs:

    • They force you to decide what happens on null at compile time, so the unsafe path simply doesn't exist in the generated code.

    • They replace defensive if (x != null) nesting with concise, total expressions.

kotlin

val name = user?.profile?.name ?: "Guest" val token = request?.header("auth") ?: return Unauthorized

Q10.
What is the purpose of the const modifier and how does it differ from a regular val?

Junior

const marks a compile-time constant whose value is inlined at the call site, whereas a regular val is a read-only property resolved at runtime through a getter.

  • const val is compile-time: The value must be known at compile time and is inlined into bytecode wherever it's used (no getter call).

  • Strict restrictions: Only primitives and String; must be top-level or in an object/companion object; cannot use custom getters or non-constant initializers.

  • Regular val: Read-only but assigned at runtime; can be any type, computed, or initialized from other values.

  • Practical effect: Use const for true constants (keys, tags); it can also be used in annotations, which a plain val cannot.

Q11.
What are 'Higher-Order Functions' and how do they enable functional programming in Kotlin?

Junior

A higher-order function is one that takes a function as a parameter or returns a function; combined with first-class function types and lambdas, this is what makes functional-style programming natural in Kotlin.

  • Function types are first-class: Types like (Int) -> String can be passed, stored, and returned just like any value.

  • Enables composition and abstraction: Standard operations like map, filter, fold take behavior as an argument instead of hard-coding it.

  • Lambdas and trailing-lambda syntax: Make call sites concise and let you build DSLs and the scope functions.

  • inline for performance: Marking a HOF inline inlines the lambda body, avoiding allocation/virtual-call overhead and enabling non-local returns.

kotlin

inline fun <T> measure(block: () -> T): T { val start = System.nanoTime() val result = block() println("took ${System.nanoTime() - start} ns") return result }

Q12.
What are default and named arguments, and how do they reduce the need for function overloading?

Junior

Default arguments let a parameter have a fallback value, and named arguments let callers pass values by name in any order; together they replace the many overloads Java needs to express optional parameters.

  • Default arguments:

    • Declared inline (e.g. greeting: String = "Hi"); omitting the argument uses the default.

    • A single function covers every combination of present/absent parameters.

  • Named arguments: Pass by name (sep = ", "), improving readability and letting you skip earlier optional params.

  • Why this reduces overloading:

    • In Java each optional combination needs its own overload; one Kotlin function with defaults expresses all of them.

    • For Java callers, add @JvmOverloads to generate the overloads automatically.

kotlin

fun connect(host: String, port: Int = 80, timeout: Int = 5000) { /* ... */ } connect("example.com") // uses both defaults connect("example.com", timeout = 1000) // skip port via named arg

Q13.
What is a typealias and what problems does it solve?

Junior

A typealias gives an existing type a new, alternative name; it introduces no new type, only a more readable shorthand resolved at compile time.

  • What it does: Purely a naming convenience: the alias and original are fully interchangeable, with no runtime cost or type-safety difference.

  • Problems it solves:

    • Shortens long generic or function types (e.g. typealias Handler = (Request) -> Response).

    • Adds domain meaning to a primitive or collection type (typealias UserId = String).

    • Disambiguates name clashes when importing types from different packages.

  • What it is NOT: Not a distinct type, so it gives no extra type safety; for that, use an inline value class instead.

Q14.
What visibility modifiers does Kotlin have, and what does the 'internal' modifier mean?

Junior

Kotlin has four visibility modifiers: public, private, protected, and internal. internal restricts visibility to the same compilation module.

  • public: The default; visible everywhere.

  • private: Visible only inside the file (top-level) or the enclosing class.

  • protected: Like private but also visible in subclasses; not available for top-level declarations.

  • internal:

    • Visible everywhere within the same module (a set of files compiled together, e.g. a Gradle module or Maven project).

    • Lets you expose APIs across packages inside your library while hiding them from external consumers.

    • Note: from Java, internal members are visible (name-mangled) since the JVM has no equivalent concept.

Q15.
How does the 'when' expression work in Kotlin, and how does it differ from Java's switch statement?

Junior

when is Kotlin's replacement for switch: it matches a value against branches, but it is an expression (returns a value), needs no break, and supports arbitrary conditions, types, and ranges.

  • It can be a statement or an expression: As an expression it returns a value, so the branches must be exhaustive (or have an else).

  • No fall-through: Only the matched branch runs, so there is no break; combine cases with commas: 0, 1 -> ....

  • Branches can be far richer than Java constants: Ranges (in 1..10), type checks (is String, with smart cast), and arbitrary boolean expressions (argument-less when).

  • Exhaustiveness: Over a sealed type or enum the compiler can verify all cases are covered, removing the need for else.

kotlin

val label = when (x) { 0, 1 -> "small" in 2..9 -> "medium" is String -> "text:$x" else -> "other" }

Q16.
Explain the difference between a sealed class and an enum class. When would you choose one over the other?

Mid

Both restrict a type to a known set of subtypes, but an enum is a fixed set of single instances (constants), while a sealed class is a closed hierarchy of distinct subtypes that can each hold their own state.

  • enum class:

    • A finite list of named singleton constants, each the same type.

    • Every constant shares the same property shape (defined once on the enum).

    • Best for a simple, closed set of values: days, states, directions.

  • sealed class:

    • A closed hierarchy where each subclass can have different properties and multiple instances.

    • Subclasses can be data classes, objects, etc., carrying heterogeneous data.

    • Best for representing variants with payloads: Result.Success(data) vs Result.Error(msg).

  • Shared trait: both enable exhaustive when without an else branch.

  • Rule of thumb: choose enum for a flat set of constant values; choose sealed when variants need distinct data or behavior.

Q17.
What is the difference between a Sealed Class and a Sealed Interface?

Mid

Both define a closed set of subtypes known at compile time, but a sealed interface is more flexible than a sealed class because a type can implement multiple sealed interfaces while it can only extend one class.

  • sealed class:

    • Single inheritance: a subtype can extend only one (like any class).

    • Can hold state and constructors, so use it when the hierarchy needs shared fields or constructor logic.

  • sealed interface:

    • Multiple inheritance of interfaces: one type can belong to several sealed hierarchies.

    • No constructors or backing state, so it is lighter and more composable.

  • Shared rule: all direct implementations must live in the same package and module (compiler enforces the closed set).

  • Both give exhaustive when checks; prefer the interface unless you need shared state.

Q18.
What is the difference between a primary and a secondary constructor, and what is the role of the init block?

Mid

The primary constructor is declared in the class header and is the main entry point, secondary constructors are extra constructors in the body that must delegate to it, and init blocks hold initialization logic that runs as part of the primary constructor.

  • Primary constructor:

    • Part of the class header: class User(val name: String).

    • Has no code body itself; its logic lives in property initializers and init blocks.

  • Secondary constructor:

    • Declared with constructor in the body.

    • Must delegate to the primary (if one exists) via this(...).

  • init block:

    • Runs when an instance is created, as part of the primary constructor.

    • Multiple init blocks and property initializers execute top to bottom, interleaved in declaration order, before any secondary-constructor body.

kotlin

class User(val name: String) { var age: Int = 0 init { require(name.isNotBlank()) { "name required" } } constructor(name: String, age: Int) : this(name) { this.age = age } }

Q19.
What is the contract between equals() and hashCode(), and why must they be overridden together?

Mid

The contract states that if two objects are equal by equals(), they must return the same hashCode(); you override them together so hash-based collections behave correctly.

  • The contract rules:

    • Equal objects (a == b) must have equal hash codes.

    • Equal hash codes do NOT require equality (collisions are allowed).

    • equals() must be reflexive, symmetric, transitive, and consistent.

  • Why both together:

    • Hash collections (HashMap, HashSet) first bucket by hash, then compare with equals().

    • If you override only equals(), equal objects may land in different buckets and never be found.

  • In Kotlin, a data class generates both consistently, so you rarely write them by hand.

Q20.
What is an object expression (anonymous object) and how does it differ from an object declaration?

Mid

An object expression creates an anonymous object (an unnamed instance, often of an interface or class) on the spot, while an object declaration defines a named singleton that is created lazily once.

  • Object expression:

    • Syntax object : SomeType { ... } produces a fresh instance each time the expression is evaluated.

    • Often used in place of Java anonymous classes (listeners, comparators); can capture and even modify variables from the enclosing scope.

    • It is an expression, so it has a type and can be assigned or returned.

  • Object declaration:

    • Syntax object Name { ... } declares a singleton: exactly one thread-safe, lazily initialized instance.

    • It is a declaration, not an expression, so you cannot assign it on the right side of =.

    • A companion object is a special case tied to its enclosing class.

  • Key difference: Expression = many instances, anonymous; declaration = one instance, named.

kotlin

// Expression: new instance, anonymous val click = object : OnClickListener { override fun onClick() = println("clicked") } // Declaration: a single named singleton object Registry { val items = mutableListOf<String>() }

Q21.
What is the difference between Unit and Nothing?

Mid

Both are special return types, but Unit means "a function completes and returns no meaningful value" (exactly one instance), while Nothing means "a function never returns normally at all" (zero instances).

  • Unit:

    • The analog of void, but it is a real type with a single value, the Unit object.

    • The default return type when none is specified; the function does return, it just carries no information.

  • Nothing:

    • Has no instances; it is the type of an expression that never produces a value (always throws or loops forever).

    • It is a subtype of every type, so throw can be used anywhere a value is expected (e.g. the right side of ?:).

    • Helps the compiler with control-flow and null analysis (e.g. TODO() returns Nothing).

kotlin

fun logged(): Unit { println("done") } // returns the Unit value fun fail(msg: String): Nothing = throw IllegalStateException(msg) val name = user.name ?: fail("no name") // Nothing fits any type

Q22.
Explain the difference between Unit, Any, and Nothing.

Mid

These three sit at the extremes of Kotlin's type hierarchy: Any is the supertype of everything, Nothing is the subtype of everything, and Unit is an ordinary type meaning "no meaningful return value."

  • Any:

    • The root of all non-nullable types (like Java's Object); every class implicitly extends it.

    • Provides equals(), hashCode(), and toString(); use it when a value can be anything.

  • Unit:

    • A regular type with exactly one value; the equivalent of void, the default return type.

    • The function does return normally, it simply carries no data.

  • Nothing:

    • Has no values at all; the type of expressions that never complete (throwing or infinite loop).

    • Being a subtype of every type, it slots into any expected type, aiding control-flow and null inference.

  • Quick contrast: Any = "could be any value"; Unit = "returns, but no value"; Nothing = "never returns."

Q23.
How does Kotlin's 'Smart Cast' work and what can prevent it from working?

Mid

Smart cast is the compiler automatically casting a value to a more specific type after you've checked it (with is, !=null, etc.), so you don't write an explicit cast. It works only when the compiler can prove the value can't change between the check and the use.

  • How it works:

    • After a condition narrows a type, the compiler tracks that fact and treats the variable as the narrowed type within that scope.

    • Common triggers: is checks, null checks (if (x != null)), and conditions combined with &&/||.

  • What prevents it:

    • A mutable var property (especially open or with a custom getter): its value could change between check and use.

    • A var captured by a closure/lambda, since another thread or call could mutate it.

    • Properties from another module, or open / overridable members where the compiler can't guarantee stability.

  • What enables it reliably:

    • Local val and immutable values are always smart-castable.

    • Workaround for unstable properties: copy into a local val first, then check that.

kotlin

fun describe(x: Any?) { if (x is String) { println(x.length) // smart cast to String } } val local = mutableObj.value // copy var into val if (local != null) local.use() // now smart cast works

Q24.
Explain the difference between launch and async. When would you use one over the other?

Mid

Both start a coroutine, but launch is fire-and-forget and returns a Job, while async is for producing a result and returns a Deferred<T> you await. Use async only when you need a value back, especially for concurrent work.

  • launch:

    • Returns Job; for side-effect work where you don't need a return value.

    • Uncaught exceptions propagate immediately to the parent scope.

  • async:

    • Returns Deferred<T>; call await() to get the result.

    • Exceptions are deferred until you await() (in the default behavior).

  • When to use which:

    • Use launch for tasks like updating UI or writing logs with no result.

    • Use async to run several operations concurrently and combine results: start multiple async then await each.

    • Anti-pattern: async { ... }.await() back-to-back is just sequential work with extra overhead.

kotlin

// Concurrent fetches with async val a = async { fetchUser() } val b = async { fetchOrders() } val result = a.await() to b.await() // Fire-and-forget with launch launch { logger.write("done") }

Q25.
What is the purpose of a CoroutineDispatcher?

Mid

A CoroutineDispatcher decides which thread or thread pool a coroutine runs on and how it is scheduled, so suspend/resume can hop threads in a controlled way.

  • Confines execution to threads: It intercepts coroutine continuations and dispatches them onto a target thread (single thread, a pool, or the UI thread).

  • It is a CoroutineContext element: You set it via the context, e.g. launch(Dispatchers.IO) or withContext(Dispatchers.Default).

  • Enables structured thread switching: Instead of manually managing threads, you switch context and the dispatcher handles resumption on the right thread.

  • Built-in dispatchers: Dispatchers.Main, Dispatchers.IO, Dispatchers.Default, and Dispatchers.Unconfined cover common cases.

Q26.
When would you use Dispatchers.Main, Dispatchers.IO, and Dispatchers.Default?

Mid

Pick the dispatcher by the kind of work: Main for UI, IO for blocking I/O, and Default for CPU-intensive computation.

  • Dispatchers.Main: Runs on the UI thread; use it to update UI or touch view state. Requires a platform (Android, JavaFX) and a main-thread library.

  • Dispatchers.IO: A large, elastic pool optimized for blocking operations: network calls, disk/file access, database queries.

  • Dispatchers.Default: A pool sized to CPU cores for CPU-bound work: sorting, parsing, JSON processing, heavy computation.

  • They share threads: IO and Default share an underlying pool, so switching between them is cheap and avoids extra thread creation.

Q27.
How does a CoroutineScope differ from a CoroutineContext?

Mid

A CoroutineContext is the set of data (dispatcher, job, name, exception handler) that defines how a coroutine runs; a CoroutineScope is a holder of that context that defines a coroutine's lifecycle and lets you launch new coroutines.

  • CoroutineContext is a map-like set of elements: Holds elements like Job, CoroutineDispatcher, CoroutineName, and CoroutineExceptionHandler; combine them with +.

  • CoroutineScope wraps a context: It is essentially just a coroutineContext property, but its purpose is to provide structured concurrency: every coroutine launched in it inherits its context and is tied to its Job.

  • Scope controls lifecycle: Cancelling the scope cancels all its children; e.g. viewModelScope cancels coroutines when the ViewModel is cleared.

  • Rule of thumb: Context = the configuration; Scope = the boundary you launch into.

Q28.
Why is it generally a bad idea to use GlobalScope in a production application?

Mid

GlobalScope launches coroutines that live for the entire application lifetime with no parent, breaking structured concurrency and inviting leaks and unhandled errors.

  • No lifecycle binding: Its coroutines aren't tied to any component, so they keep running after the screen/ViewModel is gone, causing memory leaks and wasted work.

  • No automatic cancellation: There's no parent Job to cancel; you must track and cancel each coroutine manually, which is error-prone.

  • Breaks error propagation: Failures don't propagate to a parent scope and can be silently lost or crash unexpectedly.

  • Better alternatives: Use a lifecycle-aware scope (viewModelScope, lifecycleScope) or create your own CoroutineScope with a SupervisorJob you cancel deliberately.

Q29.
What is the difference between lateinit var and by lazy property delegation?

Mid

Both defer initialization, but lateinit var is a mutable property you must assign before use, while by lazy is a read-only val computed once on first access.

  • lateinit var:

    • Mutable, can be reassigned; only for non-null, non-primitive types.

    • You initialize it yourself later (e.g. in onCreate or DI); accessing it before assignment throws UninitializedPropertyAccessException.

    • Check state with ::prop.isInitialized.

  • by lazy:

    • Read-only val; the lambda runs once on first access and caches the result.

    • Thread-safe by default (LazyThreadSafetyMode.SYNCHRONIZED); works with primitives and nullables.

  • When to use which: Use lateinit when an external party sets the value; use lazy when the value is computed on demand from within.

Q30.
Explain the difference between a 'backing field' and a 'property' in Kotlin.

Mid

A property is the public concept (a name with a getter and optionally a setter), while a backing field is the hidden storage (field) the accessors use to actually hold the value.

  • Property = accessors: Every val/var generates a getter (and setter for var); you can override them with custom logic.

  • Backing field = storage: Generated only when needed; referenced inside accessors via the field keyword to avoid infinite recursion.

  • No field is generated when: The property is fully computed (a custom getter that never uses field), making it a derived/virtual property.

  • Common use: Expose a read-only public property while mutating private state in the setter.

kotlin

var counter = 0 set(value) { if (value >= 0) field = value // field = backing storage } val isEmpty: Boolean // no backing field (computed) get() = counter == 0

Q31.
What is the difference between read-only collections (List) and mutable collections (MutableList) in Kotlin, and are read-only collections truly immutable?

Mid

A List exposes only read operations while a MutableList adds write operations, but read-only is not the same as immutable: it's a restricted view, not a guarantee that the underlying data can't change.

  • Read-only interfaces: List, Set, Map expose size, get, iteration: no add/remove.

  • Mutable interfaces extend them: MutableList adds add, set, removeAt, etc.

  • Why not truly immutable:

    • A List can be a view over a MutableList; the holder of the mutable reference can still change it.

    • Underneath, both are often the same JVM ArrayList; a cast can re-expose mutation.

  • For real immutability: Use defensive copies (toList()) or kotlinx.collections.immutable's PersistentList.

Q32.
What are the observable and vetoable property delegates from Delegates, and when would you use them?

Mid

Delegates.observable and Delegates.vetoable are property delegates that run a callback on every assignment: observable notifies you after a change, while vetoable lets you reject a change before it takes effect.

  • observable(initial) { prop, old, new -> ... }: Handler fires after the new value is assigned; ideal for reacting to state changes (logging, UI updates).

  • vetoable(initial) { prop, old, new -> Boolean }:

    • Handler runs before assignment; returning false rejects the change and the old value is kept.

    • Use for validation/invariants (e.g. reject negative values).

  • Common ground: Both give you the KProperty, old, and new values without writing a custom getter/setter.

kotlin

var name: String by Delegates.observable("") { _, old, new -> println("$old -> $new") } var age: Int by Delegates.vetoable(0) { _, _, new -> new >= 0 // rejects negatives }

Q33.
What are extension properties, and how do they differ from extension functions and regular properties?

Mid

Extension properties add a property-style accessor to an existing type without modifying it, computed via a getter (and optional setter); they differ from extension functions only in call syntax and from regular properties in that they cannot store state.

  • How they work: Declared with a receiver type and a custom get(), resolved statically at compile time.

  • No backing field: They can't store data, so field is illegal; the value must be derived from the receiver each access.

  • vs extension functions: Same mechanism; use a property when it reads like state with no arguments and is cheap, a function when it's an action or takes parameters.

  • vs regular properties: Regular properties can have backing fields and storage; extension ones are pure computed accessors and not actually members of the class.

kotlin

val String.lastChar: Char get() = this[length - 1]

Q34.
Compare the scope functions let, run, with, apply, and also. When is it idiomatic to use each?

Mid

All five run a block in the context of an object; they differ in how the object is referenced (it vs this) and what they return (the block result vs the object itself).

  • let: it, returns block result: Null-safe transforms with ?.let { } or scoping a value to a block.

  • run: this, returns block result: Compute a result while accessing members directly; also has a non-extension form for grouping statements.

  • with: this, returns block result: Like run but called as with(obj) { }; use when the object is non-null and you just need its members.

  • apply: this, returns the object: Object configuration/builder style (set several properties then return it).

  • also: it, returns the object: Side effects on the object (logging, validation) while keeping the chain.

  • Choosing: Return object: apply/also. Return result: let/run/with. Receiver this: run/with/apply. Receiver it: let/also.

Q35.
Explain the difference between fold and reduce.

Mid

Both collapse a collection into a single value, but reduce uses the first element as the seed, while fold takes an explicit initial value (and so can change the result type).

  • reduce:

    • Starts from the first element; accumulator type must match the element type.

    • Throws on an empty collection (there is no seed).

  • fold:

    • You supply the initial accumulator, so the result type can differ from the element type.

    • Returns the initial value safely for an empty collection.

  • Rule of thumb: Use fold when you need a starting value or a different result type; use reduce for a simple same-type aggregation you know is non-empty.

kotlin

val nums = listOf(1, 2, 3) val sum = nums.reduce { acc, n -> acc + n } // 6 val sumPlus10 = nums.fold(10) { acc, n -> acc + n } // 16 val concat = nums.fold("") { acc, n -> acc + n } // "123" (String result)

Q36.
How does operator overloading work in Kotlin, and which operators can you overload?

Mid

Operator overloading lets you give operators like + or [] custom meaning by defining functions with reserved names marked operator; the compiler translates the operator into the corresponding function call.

  • How it works: Define a member or extension function with a fixed name and the operator modifier; a + b compiles to a.plus(b).

  • Overloadable categories:

    • Arithmetic: plus, minus, times, div, rem.

    • Indexing/invoke: get, set, invoke.

    • Comparison/equality: compareTo (for < > <= >=), equals.

    • Others: contains (in), rangeTo (..), inc/dec, augmented assignments like plusAssign.

  • Constraint: You cannot invent new operators or change precedence; only the predefined set with their fixed names is overloadable.

kotlin

data class Vec(val x: Int, val y: Int) { operator fun plus(o: Vec) = Vec(x + o.x, y + o.y) } val v = Vec(1, 2) + Vec(3, 4) // Vec(4, 6)

Q37.
What is an infix function and when would you define one?

Mid

An infix function is a member or extension function with a single parameter marked infix, letting you call it without the dot or parentheses (a to b instead of a.to(b)) for a more readable, DSL-like syntax.

  • Requirements: Must be a member or extension function, marked infix, with exactly one parameter and no default/vararg.

  • When to use:

    • When the call reads naturally as a phrase, e.g. 1 to "one", x shl 2, or DSL builders.

    • Avoid it when the operation isn't obviously binary/relational, since precedence can surprise readers.

  • Precedence note: Infix calls bind looser than arithmetic but tighter than &&/||, so use parentheses when mixing.

kotlin

infix fun Int.times(s: String) = s.repeat(this) val line = 3 times "ab" // "ababab"

Q38.
What is the difference between a lambda and an anonymous function, particularly regarding the behavior of the 'return' statement?

Mid

Both are function literals, but they differ in syntax and especially in how return behaves: a bare return in a lambda is a non-local return from the enclosing function, while in an anonymous function it returns only from the function literal itself.

  • Lambda:

    • Concise { x -> ... } form; the last expression is the implicit return value (no return keyword).

    • A plain return is non-local: it returns from the enclosing function and is only allowed when the lambda is passed to an inline function. Use return@label to return from just the lambda.

  • Anonymous function:

    • Uses the fun keyword with no name and can declare an explicit return type.

    • return returns from the anonymous function itself (local), behaving like an ordinary function.

  • Takeaway: Choose an anonymous function when you need explicit local return semantics or an explicit return type; otherwise lambdas are more idiomatic.

kotlin

fun process(items: List<Int>) { items.forEach { if (it == 0) return } // non-local: exits process() items.forEach(fun(x) { if (x == 0) return }) // local: exits the lambda only }

Q39.
What are 'Platform Types' in Kotlin, and why are they dangerous when interoperating with Java?

Mid

A platform type is a type coming from Java whose nullability is unknown to Kotlin; the compiler relaxes null checks for it, which means a Java value that is actually null can slip past Kotlin's null safety and cause an NPE at runtime.

  • What they are: Written as T! in diagnostics (you can't write it yourself); Kotlin treats them as either T or T? at your discretion.

  • Why dangerous:

    • The compiler skips its usual null guarantees, so you can assign a platform type to a non-null variable and only fail at runtime if it was null.

    • This silently defeats Kotlin's biggest safety feature when calling Java.

  • How to stay safe:

    • Annotate Java with @Nullable/@NotNull so Kotlin infers proper nullable/non-null types.

    • At the boundary, treat values as nullable and handle them explicitly, or declare an explicit Kotlin type to force a check.

Q40.
What do the annotations @JvmStatic, @JvmOverloads, and @JvmField do when calling Kotlin code from Java?

Mid

These are interop annotations that make Kotlin declarations friendlier and idiomatic to call from Java, since Kotlin compiles some constructs differently than Java expects.

  • @JvmStatic: Applied to members of a companion object or named object, it generates a real static method/field so Java calls Foo.bar() instead of Foo.Companion.bar().

  • @JvmOverloads:

    • Generates overloaded method signatures for functions/constructors with default parameter values, since Java has no concept of default arguments.

    • One overload is produced per omittable trailing parameter.

  • @JvmField:

    • Exposes a property as a public Java field instead of generating a getter/setter, so Java accesses obj.x directly.

    • Useful for constants and POJO-style interop; not allowed with open, override, or private properties.

Q41.
What are generic type constraints (upper bounds) and the 'where' clause used for?

Mid

Type constraints restrict what types can be used as a generic argument. An upper bound (T : SomeType) requires the argument to be a subtype, and the where clause is used when a type parameter needs more than one bound.

  • Upper bound (single constraint):

    • fun <T : Number> sum(list: List<T>) only accepts subtypes of Number, so you can call Number members on T.

    • With no explicit bound, the implicit upper bound is Any?.

  • where clause (multiple constraints): Used when a single type parameter must satisfy two or more bounds at once, which the T : X syntax can't express.

  • Why it matters: Constraints give you compile-time guarantees and access to the bounded type's members without casts.

kotlin

fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String> where T : CharSequence, T : Comparable<T> { return list.filter { it > threshold }.map { it.toString() } }

Q42.
Explain the purpose of the inline keyword. What are the trade-offs regarding binary size vs. performance?

Mid

The inline keyword tells the compiler to copy a function's body (and its lambda arguments) directly into each call site instead of creating an actual function call and lambda object. Its main purpose is eliminating the runtime overhead of higher-order functions.

  • Why it exists:

    • Normally a lambda passed to a function becomes a Function object (allocation + virtual call). Inlining removes that object and the call.

    • Enables features impossible otherwise: non-local returns from lambdas and reified type parameters.

  • Performance benefit: No lambda allocation and no extra stack frame, so tight higher-order calls (e.g. map, custom DSL helpers) get faster.

  • The trade-off: binary size:

    • The body is duplicated at every call site, so inlining large functions called in many places bloats bytecode (code bloat).

    • Best for small functions taking lambdas; inlining huge bodies hurts more than it helps.

  • Rule of thumb: Use inline for small higher-order functions; for a function with no lambda parameters, inlining usually gives no meaningful win and the compiler warns.

Q43.
What are 'Value Classes' (inline classes) and how do they differ from 'Data Classes'?

Mid

A value class (declared @JvmInline value class) wraps a single value to add type safety with (usually) zero runtime allocation: the compiler erases the wrapper and uses the underlying value directly. A data class is a full object whose job is to hold and compare multiple properties.

  • Value class:

    • Holds exactly one property; the compiler inlines it so there's no heap object in most cases.

    • Used to give a primitive a meaningful type without overhead, e.g. UserId instead of a raw Int.

    • Gets boxed (allocated) only when needed: used as a nullable, generic type, or supertype.

  • Data class:

    • Can have many properties; always a real object on the heap.

    • Auto-generates equals(), hashCode(), toString(), copy(), and componentN().

  • Key contrast: Value class = lightweight type-safe wrapper, one field, no allocation; data class = data container, multiple fields, always allocated.

kotlin

@JvmInline value class Password(val value: String) // no Password object at runtime data class User(val id: Int, val name: String) // real object

Q44.
Explain 'Tail Recursion' (tailrec). How does the compiler optimize these functions?

Mid

A tailrec function is a recursive function whose recursive call is the very last operation; the compiler rewrites it into a plain loop, so deep recursion runs without growing the call stack and never overflows.

  • The requirement:

    • The recursive call must be the last thing the function does (in tail position); no work, like a multiplication, may happen after it.

    • It must call itself directly, not via another function or inside a try/catch.

  • What the compiler does:

    • It replaces the recursion with iteration (a loop that updates parameters), eliminating new stack frames.

    • This avoids StackOverflowError for large inputs while keeping the readable recursive style.

  • Safety check: If you add tailrec but the call isn't truly in tail position, the compiler warns and does not optimize.

kotlin

tailrec fun factorial(n: Int, acc: Long = 1): Long = if (n <= 1) acc else factorial(n - 1, acc * n) // call is the last operation

Q45.
What is the difference between an Iterable (List) and a Sequence? When should you use a Sequence for performance?

Mid

Both represent a series of elements, but a collection like List evaluates each operation eagerly across the whole collection, while a Sequence evaluates lazily, processing one element through the entire chain at a time. Sequences win when you chain many operations over large data.

  • Iterable / List: eager, horizontal:

    • Each operation (map, filter) runs over all elements and creates a new intermediate list before the next step.

    • Many chained steps = many intermediate collections = more allocation.

  • Sequence: lazy, vertical:

    • Each element flows through the whole operation chain before the next element starts; no intermediate collections.

    • Operations only run when a terminal operation (toList, first, sum) is called.

    • Supports short-circuiting: first can stop after one matching element instead of processing everything.

  • When to choose a Sequence:

    • Large datasets with multiple chained transformations, or when you'll short-circuit.

    • For small collections or a single operation, a List is usually faster (sequences add per-element overhead).

kotlin

// Eager: builds an intermediate list, processes all 1M items per step list.map { it * 2 }.filter { it > 10 }.first() // Lazy: stops as soon as the first match is produced list.asSequence().map { it * 2 }.filter { it > 10 }.first()

Q46.
Explain the difference between Flow and Channel.

Mid

A Flow is a cold, declarative stream that re-runs its producer for each collector, whereas a Channel is a hot, queue-like primitive for communicating values between coroutines, where each value is received by exactly one consumer.

  • Flow:

    • Cold: nothing runs until collect; each collector gets its own fresh execution.

    • Declarative pipeline with operators (map, filter); great for representing a stream of computed data.

  • Channel:

    • Hot: a communication conduit, like a coroutine-safe queue with send and receive.

    • Fan-out semantics: each element is consumed by only one receiver, not broadcast.

    • Stateful and must be closed; suited to producer/consumer handoff between coroutines.

  • Choosing:

    • Use Flow to expose/transform streams of data; use Channel to pass values between coroutines.

    • To broadcast hot values to many collectors, prefer SharedFlow over a raw Channel.

Q47.
What is the difference between intermediate and terminal operators in a Flow?

Mid

Intermediate operators transform a flow and return a new flow lazily (nothing runs yet); terminal operators actually start collection and run the upstream pipeline.

  • Intermediate operators:

    • Examples: map, filter, onEach, transform.

    • Cold and lazy: they just build a new Flow description and don't suspend or execute code.

  • Terminal operators:

    • Examples: collect, toList, first, reduce, single.

    • They are suspend functions that trigger the flow and run the whole chain.

  • Key consequence: Without a terminal operator, nothing executes; this is what makes flows cold.

Q48.
What are 'Extension Functions'? How are they resolved at runtime, and can they access private members of the class they extend?

Mid

Extension functions let you add functions to an existing type without modifying or inheriting it; they are resolved statically at compile time based on the declared (static) type, and they cannot access private or protected members of the receiver.

  • What they are: Syntactic sugar: the compiler generates a static method taking the receiver as its first parameter.

  • Static (compile-time) resolution:

    • Dispatch is based on the declared type of the expression, not the runtime type, so they are not polymorphic / not overridable.

    • If a member function and extension have the same signature, the member always wins.

  • Visibility: They only see the public/internal API of the type, just like external code; no access to its private state.

kotlin

open class Shape class Circle : Shape() fun Shape.name() = "shape" fun Circle.name() = "circle" val s: Shape = Circle() println(s.name()) // "shape" — resolved by declared type Shape

Q49.
How does 'Interface Delegation' (using the by keyword) help in favoring composition over inheritance?

Mid

Interface delegation with by lets a class implement an interface by forwarding its calls to a held instance, giving you reuse through composition without subclassing or boilerplate forwarding methods.

  • How it works: class A(b: B) : I by b makes the compiler generate forwarding implementations of I's members that call b.

  • Composition over inheritance:

    • You reuse behavior by holding an object rather than extending a base class, avoiding fragile deep hierarchies.

    • You can compose multiple delegates (multiple interfaces from different objects), which inheritance can't do.

  • Flexibility:

    • Selectively override any delegated member while letting the rest forward.

    • The delegate is decided at construction, so behavior can be swapped at runtime.

kotlin

interface Repository { fun load(): String } class DbRepository : Repository { override fun load() = "db" } // Reuses DbRepository by composition, no inheritance class CachingRepository(repo: Repository) : Repository by repo { override fun load() = "cached:" + "..." }

Q50.
What are destructuring declarations and how do they work under the hood with componentN functions?

Mid

Destructuring lets you unpack an object into multiple variables in one declaration; the compiler translates it into calls to component1(), component2(), etc., on that object.

  • Syntax maps positionally to componentN: val (a, b) = p compiles to val a = p.component1(); val b = p.component2().

  • Data classes get componentN for free: The compiler generates one componentN per primary-constructor property, in declaration order.

  • Any type can support it: Declare operator fun componentN() (even as extensions) and the type becomes destructurable; Map.Entry and Pair already do.

  • Position matters, not names: Variables bind by order; use _ to skip a component you don't need.

  • Works in lambdas and loops: e.g. for ((k, v) in map) and map { (k, v) -> ... }.

kotlin

data class Point(val x: Int, val y: Int) val (x, y) = Point(1, 2) // x = p.component1(), y = p.component2()

Q51.
How do you define and use custom annotations in Kotlin, and what are annotation use-site targets?

Mid

You define an annotation with the annotation class keyword and control where and how long it applies with meta-annotations; use-site targets disambiguate which underlying Java element (field, getter, parameter) a Kotlin declaration's annotation actually lands on.

  • Declaring an annotation: annotation class Tag(val name: String); parameters must be compile-time constants (primitives, String, KClass, enums, other annotations, or arrays of these).

  • Meta-annotations configure it: @Target restricts where it can be applied, @Retention sets SOURCE/BINARY/RUNTIME visibility, plus @Repeatable.

  • Use-site targets: A property compiles to a field, getter, setter, and maybe a constructor parameter; a prefix like @field:, @get:, @param:, or @set: picks the precise target.

  • Why it matters: Frameworks (e.g. JPA, Jackson) often require the annotation on the field rather than the getter, so the target prefix is essential for correct behavior.

kotlin

@Target(AnnotationTarget.FIELD) @Retention(AnnotationRetention.RUNTIME) annotation class Column(val name: String) data class User(@field:Column("user_name") val name: String)

Q52.
Why does Kotlin not have 'Checked Exceptions' like Java? What are the architectural tradeoffs of this design?

Senior

Kotlin deliberately omits checked exceptions because experience with Java showed they hurt scalability and lead to bad error handling without improving reliability. Every exception in Kotlin is effectively unchecked.

  • The Java problem Kotlin reacts to:

    • Forced throws declarations and try/catch pollute signatures and propagate up large call chains.

    • In practice developers write empty or swallow-all catch blocks, which is worse than no checking.

  • Benefits of dropping them:

    • Cleaner signatures and easier composition with lambdas, higher-order functions, and streams (which checked exceptions break in Java).

    • Better Java interop: Kotlin can call Java methods that declare checked exceptions without ceremony.

  • The tradeoff:

    • The compiler no longer reminds you which calls can fail, so recoverable errors can go unhandled silently.

    • Kotlin shifts the burden to discipline and design: model expected failures as return values (sealed Result types, nullable returns) and reserve exceptions for truly exceptional cases.

    • Use @Throws to declare exceptions for Java/JVM callers that need the metadata.

Q53.
How does Kotlin handle primitive types (like Int, Boolean) under the hood to ensure performance while appearing as objects?

Senior

On the JVM, Kotlin maps types like Int and Boolean to Java primitives (int, boolean) wherever possible, and only boxes them into wrapper objects when an object reference is genuinely required, giving primitive performance with an object-like API in source.

  • No separate primitive/wrapper types in source: You always write Int, not int vs Integer; the compiler decides the bytecode representation.

  • When it stays primitive: A non-nullable Int used directly compiles to int: no allocation, stack-stored.

  • When it gets boxed:

    • Nullable types (Int?) need to represent null, so they box to java.lang.Integer.

    • Generics use boxed types (List<Int> holds Integer) because the JVM erases generics to Object.

  • Performance note: Boxing two equal Int? values can produce different references, which is why === on boxed numbers is unreliable; use ==. Specialized arrays like IntArray avoid boxing entirely.

Q54.
How does Kotlin's null safety work under the hood at the JVM level?

Senior

At the JVM level there is no separate nullable type: nullability is a compile-time construct. String and String? are both just java.lang.String bytecode; the compiler enforces null safety statically and inserts runtime checks at boundaries.

  • Compile-time enforcement: The type system tracks T vs T? and rejects unsafe dereferences before bytecode is even generated.

  • Nullability annotations: Generated bytecode carries @Nullable / @NotNull metadata so Java callers and tooling see the contracts.

  • Runtime intrinsics checks: For non-null parameters and platform-type returns, the compiler inserts calls like Intrinsics.checkNotNullParameter that throw early with a clear message.

  • Platform types: Values from Java (no annotation) become platform types (String!): the compiler relaxes checks, so misuse can still produce an NPE.

Q55.
How does Kotlin 2.0 handle smart casting differently than previous versions?

Senior

Kotlin 2.0's K2 compiler rebuilt smart casting on a new control-flow analysis engine, so it tracks type information more precisely and smart-casts in many cases that previously required an explicit cast.

  • Smarter data-flow analysis: K2 unifies how it reasons about flow, so narrowing carries further across branches and statements.

  • New cases that now smart-cast:

    • Type checks combined with || (a common type in both branches) now narrow after the condition.

    • Variables captured in inline lambdas can be smart-cast where the analysis can prove safety.

    • A variable assigned the result of a check (val b = a is String) can drive smart casting when b is used.

  • Still unchanged: Mutable var properties with custom getters or open members still can't be smart-cast: the fundamental stability rule remains.

Q56.
What is a suspend function and how does the Kotlin compiler implement it using the CPS (Continuation Passing Style) transformation?

Senior

A suspend function is one that can pause and resume without blocking a thread. The compiler implements this by rewriting it into a state machine using Continuation Passing Style: each suspension point becomes a state, and a hidden Continuation parameter carries the callback to resume execution.

  • The hidden parameter: The compiler appends a Continuation<T> argument; suspend fun foo(): T becomes foo(c: Continuation<T>): Any?.

  • State machine transformation:

    • Code between await-like suspension points is split into labeled states; local variables are stored as fields so state survives suspension.

    • On resume, the function is re-entered and jumps to the saved state via a label.

  • COROUTINE_SUSPENDED:

    • When a call actually suspends, it returns the marker COROUTINE_SUSPENDED; the caller returns too, freeing the thread.

    • Later, continuation.resumeWith(...) drives the machine forward.

  • Result: Sequential-looking code runs as non-blocking callbacks, with no thread held during the wait.

Q57.
Explain the concept of 'Structured Concurrency'. Why is it preferred over global or unmanaged scopes?

Senior

Structured concurrency means every coroutine is launched within a CoroutineScope and its lifetime is bound to that scope: a parent doesn't complete until all its children do, and cancelling the parent cancels the children. It makes concurrency hierarchical and predictable rather than a set of orphaned tasks.

  • Core guarantees:

    • Parent Job awaits all children: no work is silently left running.

    • Cancellation propagates down the hierarchy automatically.

    • A failing child cancels siblings and propagates to the parent (unless using SupervisorJob).

  • Why it beats unmanaged scopes:

    • GlobalScope launches detached coroutines that outlive their context, leaking resources and ignoring cancellation.

    • Errors in detached tasks are easy to lose; structured scopes give clear exception propagation.

    • Lifecycle binding (e.g. viewModelScope) cancels work automatically when the owner is destroyed.

  • In practice: coroutineScope { ... } creates a child scope that returns only after all launched children finish.

kotlin

suspend fun loadAll() = coroutineScope { val a = launch { loadA() } val b = launch { loadB() } // returns only after a and b both complete }

Q58.
How does Kotlin handle 'Delegation' (by keyword) and what are its performance implications?

Senior

The by keyword lets one object hand off responsibility to another: either implementing an interface by forwarding to a delegate object, or implementing a property's get/set via a delegate. The compiler generates the boilerplate, so overhead is minimal.

  • Class/interface delegation: class A(b: B) : I by b auto-generates forwarding methods that call b, favoring composition over inheritance.

  • Property delegation: val x by lazy { ... } or var y by Delegates.observable(...); the delegate provides getValue/setValue operator functions.

  • Performance implications:

    • Each property access goes through an extra delegate object and a getValue call instead of a direct field read, a small indirection cost.

    • Map-backed delegates (by map) add a hash lookup; lazy adds synchronization unless you choose a cheaper LazyThreadSafetyMode.

    • Generally negligible, but avoid delegation in hot loops where a plain field would do.

Q59.
What are 'Explicit Backing Fields' in Kotlin 2.x, and what problem do they solve regarding property encapsulation?

Senior

Explicit backing fields let you declare a property whose field has a different (usually wider/mutable) type than the property's public API exposes, solving the classic MutableList backing a read-only List boilerplate.

  • The problem it solves: Previously you wrote a private _items: MutableList plus a public val items: List get() = _items : two declarations for one concept.

  • The new syntax: Declare one property, give the field an explicit type with field, and expose a narrower getter type.

  • Encapsulation benefit: Internal code mutates the field directly; external callers only see the read-only type, with no leak of the mutable reference.

  • Status note: It is a preview/experimental feature gated behind a language flag in Kotlin 2.x, intended to remove the backing-property idiom.

kotlin

class Repo { val items: List<String> field = mutableListOf() // field is MutableList, API is List fun add(x: String) { items.add(x) } // internal mutation }

Q60.
What are the thread-safety modes of 'by lazy' (LazyThreadSafetyMode), and when would you change the default?

Senior

by lazy takes a LazyThreadSafetyMode that controls how its single initialization is synchronized; the default (SYNCHRONIZED) is thread-safe, and you only relax it when you know the access pattern.

  • SYNCHRONIZED (default): Locks so only one thread runs the initializer; the value is computed exactly once even under concurrent access.

  • PUBLICATION:

    • Multiple threads may run the initializer concurrently, but the first result to be set wins and is published to all.

    • Use when the initializer is cheap/idempotent and you want to avoid lock contention.

  • NONE:

    • No synchronization at all: fastest, but unsafe if accessed from multiple threads.

    • Use only when the property is confined to a single thread (e.g. UI thread).

  • When to change the default: Switch to NONE for guaranteed single-thread access to skip lock overhead; otherwise keep SYNCHRONIZED.

kotlin

val config by lazy(LazyThreadSafetyMode.NONE) { loadConfig() }

Q61.
How do you handle exceptions in Coroutines, and what is the role of SupervisorJob?

Senior
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.

Q62.
Explain 'Declaration-site variance' (in and out) and how it differs from Java's 'Use-site variance' (wildcards).

Senior
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.

Q63.
What is the K2 Compiler and what are its primary benefits for developers?

Senior
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.

Q64.
What is 'Star Projection' and when is it used instead of a specific type or Any?

Senior
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.

Q65.
What is the reified keyword? Why can it only be used with inline functions, and what problem does it solve regarding type erasure?

Senior
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.

Q66.
Explain the difference between inline, noinline, and crossinline.

Senior
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.

Q67.
What are inline classes (value classes) and what performance benefits do they provide?

Senior
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.

Q68.
What is the difference between noinline and crossinline parameters in an inline function?

Senior
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.

Q69.
What is the difference between a Cold Stream (Flow) and a Hot Stream (Channel/SharedFlow)?

Senior
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.

Q70.
How does StateFlow differ from SharedFlow, and when would you use each?

Senior
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.

Q71.
What is the difference between flatMapConcat, flatMapMerge, and flatMapLatest in Kotlin Flow?

Senior
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.

Q72.
What is the difference between a Channel and a SharedFlow, and what are the buffering/overflow strategies for a Channel?

Senior
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.

Q73.
What are 'Context Parameters' in Kotlin 2.x and how do they differ from the deprecated 'Context Receivers'?

Senior
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.

Q74.
What is the Kotlin 'Contracts API' and when would you use it?

Senior
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.

Q75.
What is 'Name Mangling' in Kotlin, and why is it used for internal visibility and value classes?

Senior
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.

Q76.
How does Kotlin reflection work, and what is KClass versus Java's Class?

Senior
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.

Q77.
What is the difference between supervisorScope and coroutineScope?

Senior
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.

Q78.
Explain the roles of CoroutineScope, CoroutineContext, and Dispatcher. How do they interact?

Senior
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.

Q79.
What happens to a child coroutine when its parent job is cancelled? How can you prevent a child's failure from cancelling the parent?

Senior
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.

Q80.
Explain the difference between withContext and Dispatchers.IO for blocking operations.

Senior
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.

Q81.
How does exception propagation work in a SupervisorJob vs. a regular Job?

Senior
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.

Q82.
How does coroutine cancellation work, and why is it described as cooperative?

Senior
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.