Interview kitsBlog

Your dream job? Lets Git IT.
Interactive technical interview preparation platform designed for modern developers.

XGitHub

Platform

  • Categories

Resources

  • Blog
  • About the app
  • FAQ
  • Feedback

Legal

  • Privacy Policy
  • Terms of Service

© 2026 LetsGit.IT. All rights reserved.

Kotlin

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

Topics

Val vs Var?

easykotlinvariablesimmutability
Open question

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.

What is a data class in Kotlin and why use it?

easydata-classkotlindto+1
Open question

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.

Explain null safety in Kotlin.

easynull-safetynullableelvis-operator+1
Open question

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.

What are coroutines and how do they differ from threads?

mediumcoroutinesconcurrencysuspend+1
Open question

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.

Sealed classes vs enums in Kotlin?

mediumsealed-classenumwhen+1
Open question

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.

`val` vs `var` — what’s the difference?

easyvalvarimmutability
Open question

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.

Kotlin null-safety — what do `?`, `?.`, `?:`, and `!!` mean?

easynull-safetyelvisnullable
Open question

Answer

`T?` is a nullable type, `?.` is a safe call, `?:` (Elvis) provides a default, and `!!` asserts non-null (throws if it’s null).

What does a `data class` give you (and when is it a good fit)?

easydata-classdtovalue-object
Open question

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.

Scope functions (`let`, `apply`, `also`, `run`, `with`) — how do they differ?

mediumscope-functionsletapply+1
Open question

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.

`launch` vs `async` — what’s the difference in coroutines?

mediumcoroutinesasynclaunch+1
Open question

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

`sealed class` vs `enum` — when would you choose `sealed`?

mediumsealedenumwhen
Open question

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.

What is structured concurrency and why does it matter?

hardstructured-concurrencycancellationcoroutines
Open question

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

Flow vs Sequence — what’s the key difference?

hardflowsequencestreams
Open question

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.

What are reified generics and why do they require `inline`?

hardreifiedinlinegenerics
Open question

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.

Java interop — what are platform types (`String!`) and why are they risky?

hardinteropplatform-typesnullability
Open question

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.

What is `lateinit` and when can you use it?

easylateinitnull-safetyproperties
Open question

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.

What does `suspend` mean in Kotlin coroutines (and what does it NOT mean)?

mediumcoroutinessuspenddispatcher
Open question

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.

Extension functions — what are they and what is a common pitfall?

mediumextensionsdispatchkotlin-basics
Open question

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.

Job vs SupervisorJob — how do failures propagate?

hardcoroutinesjobsupervisorjob+1
Open question

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.

StateFlow vs SharedFlow — what’s the practical difference?

hardstateflowsharedflowflow+1
Open question

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.

What does `by lazy` do and when is it useful?

easylazydelegatesinitialization
Open question

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.

What does `inline` do and when can it help performance?

mediuminlineperformancelambdas
Open question

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

What is destructuring in Kotlin and where do `componentN()` functions come from?

mediumdestructuringdata-classcomponentN
Open question

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

What is a value class (`@JvmInline`) and why would you use it?

hardvalue-classtype-safetydomain
Open question

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

Coroutine cancellation is cooperative — what does that mean in practice?

hardcoroutinescancellationisActive+1
Open question

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.

List vs MutableList in Kotlin: what’s the difference?

easykotlincollectionsimmutability+1
Open question

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.

Generics variance: what do `out` and `in` mean in Kotlin?

mediumkotlingenericsvariance+1
Open question

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.

Channel vs Flow - how are they different in coroutines?

mediumkotlincoroutinesflow+1
Open question

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.

Dispatchers in Kotlin coroutines: `Default` vs `IO` vs `Main`?

hardkotlincoroutinesdispatchers+1
Open question

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

Coroutine exceptions: how do they propagate and when to use `CoroutineExceptionHandler`?

hardkotlincoroutinesexceptions+1
Open question

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.

Kotlin smart casts: when do they work and when do they not?

easykotlinsmart-casttype-safety+1
Open question

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.

`when` as an expression: what does it mean that it is exhaustive?

mediumkotlinwhensealed+1
Open question

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.

Interface delegation (`by`): what does it do and what is one limitation?

mediumkotlindelegationby+1
Open question

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.

`tailrec`: what does it do and when can Kotlin optimize recursion?

hardkotlintailrecrecursion+1
Open question

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.

Inline lambdas and non-local returns: what are `crossinline` and `noinline` for?

hardkotlininlinecrossinline+2
Open question

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.

Extension functions: how are they dispatched and what is a common pitfall?

mediumkotlinextensiondispatch+1
Open question

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.

Object declarations: what are they and when would you use them?

easykotlinobjectsingleton+1
Open question

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.

Companion object vs top-level members: when would you use each?

easykotlincompaniontop-level+1
Open question

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

`lateinit` vs nullable property: when would you choose `lateinit`?

mediumkotlinlateinitnull-safety+1
Open question

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

Delegated properties (`by`): what problem do they solve?

mediumkotlindelegationproperties+1
Open question

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

Operator overloading: what does `operator` enable and what’s a caveat?

mediumkotlinoperatoroverloading+1
Open question

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.

What does `typealias` do and what doesn’t it do?

easykotlintypealiastypes+1
Open question

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.

StateFlow vs SharedFlow: what’s the difference?

hardkotlinstateflowsharedflow+1
Open question

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.

coroutineScope vs supervisorScope: how do they handle failures?

hardkotlincoroutinessupervisor+1
Open question

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.