Skip to content

Kotlin Developer interview questions

100 real questions with model answers and explanations for Kotlin Developer candidates.

See a Kotlin Developer resume example

Practice with flashcards

Spaced repetition · Hunter Pass

Questions

generics

An out parameter makes a generic type covariant, so it can safely produce values of that type but cannot consume them in unsafe positions.

  • Producer<Cat> can be used where Producer<Animal> is expected because every produced Cat is an Animal.
  • The type parameter may appear in return types, but regular method parameters using it are rejected by the compiler.
  • I use out for read-only sources such as Iterable<T>, not for mutable containers that both read and write T.

Why interviewers ask this: The interviewer is checking whether the candidate can connect covariance to a safe producer API rather than only recall the keyword.

genericsapi

An in parameter makes a generic type contravariant, which is appropriate when the API only consumes values of that type.

  • Consumer<Animal> can stand in for Consumer<Cat> because it already accepts every Cat as an Animal.
  • The type parameter may appear in input positions, while returning it as a specific T would be unsafe.
  • Comparators and event handlers are common examples because they receive values without promising to produce the same subtype.

Why interviewers ask this: The interviewer is evaluating whether the candidate understands consumer variance and assignability in practical APIs.

generics

I use a star projection when the concrete type argument is unknown and the operation does not require pretending that it is Any.

  • Reading from List<*> is safe, but the result type is Any? because the element type is not known.
  • Writing non-null values to MutableList<*> is forbidden because the actual list might hold a narrower type.
  • Star projections suit inspection APIs such as logging collection size, while typed business logic should preserve the real argument.

Why interviewers ask this: The interviewer is checking whether the candidate distinguishes safe unknown types from unchecked casts and List<Any?>.

functionskotlininline

inline asks the compiler to copy the function and eligible lambda bodies to the call site, while noinline keeps a selected lambda as an object.

  • Inlining can remove lambda allocation and virtual invocation in small, frequently called higher-order utilities.
  • An inline lambda supports non-local return unless another restriction applies, which changes control-flow semantics.
  • A noinline lambda can be stored, passed to a non-inline function, or returned, but it keeps normal allocation and return behavior.

Why interviewers ask this: The interviewer is evaluating whether the candidate understands both the performance and control-flow consequences of inlining.

lambdainline

crossinline forbids a non-local return when an inline lambda may execute from a different or deferred control-flow context.

  • It is needed when the function places the lambda inside another object or callback that invokes it later.
  • The lambda is still inlined, so crossinline is not the same as noinline and does not permit storing it freely.
  • Local returns with a label remain available, letting the lambda skip its own work without returning from the caller.

Why interviewers ask this: The interviewer is checking whether the candidate can protect an inline API from invalid non-local control flow.

generics

A reified parameter lets an inline function use the concrete type argument at the call site without requiring a separate Class or KClass token.

  • The function can perform checks such as value is T or request serializer<T>() because the call site supplies type information.
  • Reified is available only on inline function type parameters, not on classes or ordinary functions.
  • Nested generic arguments remain subject to JVM erasure, so checking List<String> still cannot prove every element is a String.

Why interviewers ask this: The interviewer is evaluating whether the candidate understands what reification restores and what JVM erasure still prevents.

delegation

A delegated property forwards reads and writes to an object implementing getValue and, for var, setValue.

  • The delegate receives the owning object and KProperty metadata, so it can key storage or validation by property.
  • I use a custom delegate for repeated property behavior such as normalized settings access, not to hide complex domain workflows.
  • provideDelegate can validate or register the binding when the property is created rather than on its first access.

Why interviewers ask this: The interviewer is checking whether the candidate understands the delegation protocol and can keep abstraction boundaries explicit.

concurrencykotlin

The lazy mode controls whether concurrent first access synchronizes initialization and whether more than one initializer call is allowed.

  • SYNCHRONIZED runs the initializer once under a lock and safely publishes the value across threads.
  • PUBLICATION may run the initializer concurrently, but all callers ultimately observe one published result, so initialization must tolerate repeats.
  • NONE has no synchronization and is appropriate only when access is confined to one thread, such as known UI-thread state.

Why interviewers ask this: The interviewer is evaluating whether the candidate chooses lazy semantics from actual concurrency constraints.

delegation

observable reports a change after assignment, while vetoable decides before assignment whether the new value is accepted.

  • observable receives the property, old value, and new value, making it useful for lightweight notifications or diagnostics.
  • vetoable returns a Boolean and preserves the old value when validation fails.
  • Neither provides transactionality or thread safety, so shared domain state usually belongs behind a synchronized state holder instead.

Why interviewers ask this: The interviewer is checking whether the candidate understands callback timing and avoids treating delegates as a full state-management system.

kotlinclassesdesign

A value class is useful for a domain-specific wrapper that improves type safety while often avoiding a separate wrapper allocation.

  • UserId and OrderId can both wrap String yet remain incompatible at compile time, preventing accidental argument swaps.
  • Boxing can still occur at generic, nullable, interface, or Java boundaries, so allocation removal is not guaranteed everywhere.
  • Its single underlying value and restricted identity semantics make it unsuitable for entities that need mutable state or object identity.

Why interviewers ask this: The interviewer is evaluating whether the candidate balances domain typing with value-class representation and interoperability limits.

designapisealed-classes

A sealed hierarchy defines a closed set of direct variants, allowing callers to handle every known case with an exhaustive when expression.

  • A result API can model Success, ValidationError, and NetworkError without nullable fields or invalid combinations.
  • Adding a new variant creates compile-time failures at exhaustive consumers, exposing every place that needs a policy decision.
  • I keep the hierarchy near the domain boundary and avoid an else branch when callers should be forced to acknowledge new cases.

Why interviewers ask this: The interviewer is checking whether the candidate uses sealed types to make state spaces and API evolution explicit.

lambdakotlin

A lambda with receiver makes a configured object the implicit receiver, so DSL operations can be called without repeatedly naming that object.

  • An API such as route { get { } } exposes only members available on the relevant receiver type.
  • Nested receiver scopes create readable builders while the compiler still checks argument types and available operations.
  • I keep builders declarative and side effects explicit because hidden I/O inside innocent-looking DSL blocks makes behavior hard to test.

Why interviewers ask this: The interviewer is evaluating whether the candidate understands the mechanism and usability trade-offs behind Kotlin DSLs.

kotlin

@DslMarker prevents accidental access to receivers from an outer DSL scope when a more specific nested receiver is active.

  • Without it, a nested block may silently call a same-named function on the outer builder and configure the wrong object.
  • Annotating related receiver types makes implicit access choose the nearest marked receiver and rejects ambiguous outer calls.
  • An outer receiver can still be addressed explicitly when the DSL intentionally allows that escape hatch.

Why interviewers ask this: The interviewer is checking whether the candidate can make nested receiver scopes safe rather than merely concise.

kotlin-symbol-processing

I choose reflection for dynamic runtime discovery and KSP when the mapping can be generated at build time with stronger checks and lower runtime cost.

  • Reflection can inspect classes unavailable during the application's compilation, but it adds lookup cost and can complicate shrinking or native targets.
  • KSP reads Kotlin symbols and generates source without loading application classes, so errors can point to annotated declarations during compilation.
  • KSP adds generated-code and build-tooling complexity, so a small infrequent runtime inspection may not justify a processor.

Why interviewers ask this: The interviewer is evaluating whether the candidate can trade runtime flexibility against compile-time generation and maintenance cost.

retentionapi

Retention determines whether an annotation exists only in source, in compiled metadata, or remains visible for runtime reflection.

  • SOURCE suits lint-like checks and generators that do not need the annotation in the output artifact.
  • BINARY keeps metadata in the class file for tooling but does not promise Java reflection visibility at runtime.
  • RUNTIME is required for reflective frameworks, and its targets should be narrowed to the declarations the framework actually supports.

Why interviewers ask this: The interviewer is checking whether the candidate can define annotation contracts that match their processor or runtime consumer.

A Sequence is preferable when a multi-step pipeline can process elements lazily and avoid large intermediate collections.

  • filter followed by map over a large list can fuse element-by-element and stop early with first or take.
  • Sequence adds iterator and lambda overhead, so a short pipeline over a small collection can be faster and clearer when eager.
  • Terminal operations trigger execution, and repeated terminal operations rerun the pipeline unless the result is materialized.

Why interviewers ask this: The interviewer is evaluating whether the candidate understands lazy execution costs instead of assuming Sequence is always faster.

concurrencykotlincoroutines

Structured concurrency binds child work to a scope so lifetime, completion, cancellation, and failure follow an explicit job hierarchy.

  • A parent does not complete until its children finish, which prevents hidden work from escaping a request or screen lifecycle.
  • Cancelling the parent propagates to children, while an unhandled regular child failure normally cancels siblings through the parent.
  • coroutineScope creates a bounded child scope without blocking the underlying thread and returns only after its work settles.

Why interviewers ask this: The interviewer is checking whether the candidate reasons about coroutine ownership rather than only launch syntax.

coroutines

CoroutineScope supplies a CoroutineContext for new coroutines, and the Job inside that context defines their lifecycle relationship.

  • A context is a keyed set of elements such as Job, dispatcher, CoroutineName, and exception handler.
  • A builder inherits the scope context, replaces explicitly supplied elements, and normally creates a child Job.
  • Owning code should cancel the scope's Job at its lifecycle boundary instead of exposing an unmanaged global scope.

Why interviewers ask this: The interviewer is evaluating whether the candidate can compose execution settings with explicit lifecycle ownership.

coroutine-dispatchers

I choose a dispatcher from the work type and resource limit, not from whether the function happens to be suspending.

  • Default is sized for CPU-bound work, so blocking database or file calls can starve unrelated calculations there.
  • IO accommodates blocking I/O, but unlimited parallel calls can still overload a downstream database or HTTP service.
  • limitedParallelism or a dedicated executor is useful when a dependency needs a stricter concurrency cap than the shared pool.

Why interviewers ask this: The interviewer is checking whether the candidate connects dispatchers to blocking behavior and downstream capacity.

coroutinesstructured-concurrency

I use supervisorScope when sibling tasks must remain independent even if one child fails, while still sharing a bounded parent lifetime.

  • In coroutineScope, an unhandled child failure cancels the scope and its other children.
  • In supervisorScope, one child failure does not cancel siblings, but each child still needs an explicit error path.
  • The supervisor block itself still fails if its own body throws, so supervision is selective isolation rather than ignored errors.

Why interviewers ask this: The interviewer is evaluating whether the candidate deliberately chooses failure propagation rather than using supervision everywhere.

Locked questions

  • 21

    How do exception semantics differ between launch and async?

    asyncerror-handlingcoroutines
  • 22

    Why is coroutine cancellation cooperative, and how should application code preserve it?

    resiliencecoroutines
  • 23

    When is NonCancellable appropriate in coroutine cleanup?

    coroutines
  • 24

    When is a Channel a better fit than Flow or a direct suspending call?

    coroutinesflowconcurrency
  • 25

    How do Channel capacity and overflow policy affect backpressure?

    capacitybackpressureconcurrency
  • 26

    What are the practical consequences of cold and hot Flow semantics?

    flow
  • 27

    How do buffer, conflate, and collectLatest differ when a Flow collector is slow?

    flowflow-operators
  • 28

    Why is flatMapLatest useful for search or selection-driven data?

  • 29

    How does combine behave, and when is it preferable to zip?

  • 30

    How do you choose between StateFlow and SharedFlow for state and events?

    flow
  • 31

    What do stateIn and shareIn change about a cold Flow?

    flow
  • 32

    How should source sets such as commonMain, androidMain, iosMain, and jvmMain be used in Kotlin Multiplatform?

    kmpkotlin
  • 33

    When should Kotlin Multiplatform code use expect and actual declarations?

    kmpkotlin
  • 34

    How do you keep platform APIs from leaking into shared Kotlin Multiplatform code?

    kmpapikotlin
  • 35

    What should you consider when evolving models encoded with kotlinx.serialization?

    serializationkotlin
  • 36

    How do Ktor plugins participate in the application pipeline?

    ktorci-cd
  • 37

    How would you test a Ktor route without starting a real server?

    ktor
  • 38

    Why can final Kotlin classes and methods cause problems with Spring Boot proxies?

    kotlin
  • 39

    How should suspend boundaries be handled in a Spring Boot Kotlin service?

    kotlincoroutines
  • 40

    What caveats apply when combining Spring transactions with suspending code?

    transactionscoroutines
  • 41

    When should multiple Room operations be wrapped in one transaction?

    transactionsroom
  • 42

    How would you design an offline-first repository around Room and a remote API?

    roomapidesign
  • 43

    What does state hoisting mean in Jetpack Compose, and where should state live?

    composehoisting
  • 44

    How do you choose among LaunchedEffect, DisposableEffect, and SideEffect in Compose?

    compose-effectscoroutines
  • 45

    What are the practical benefits and pitfalls of the Gradle Kotlin DSL?

    buildgradle-kotlin-dslkotlin
  • 46

    Why use convention plugins for a multi-module Gradle build?

    build
  • 47

    What commonly breaks Gradle incremental builds and the configuration cache?

    configbuildcaching
  • 48

    Why is KSP generally preferred over kapt for Kotlin annotation processing?

    kotlin-symbol-processingannotation-processingconcurrency
  • 49

    How would you use Detekt and Kotest without turning them into ceremony?

  • 50

    How would you define module boundaries in a Kotlin codebase?

    kotlin
  • 51

    An Android repository starts a polling coroutine in GlobalScope, and polling continues after the user leaves the screen. How would you fix and verify the leak?

    coroutines
  • 52

    A sync function catches Exception to log failures, and cancelling its parent no longer stops uploads. What would you change?

    error-handling
  • 53

    A Compose button calls runBlocking around a suspend checkout request, and the UI freezes for two seconds. How would you repair it?

    coroutine-testingcoroutines
  • 54

    A service test deadlocks because code already running on a single-thread dispatcher calls runBlocking and launches child work onto that same dispatcher. How do you diagnose and fix it?

    concurrencycoroutineslocking
  • 55

    Eight CPU-heavy image hashes run on Dispatchers.Default, and unrelated request coroutines now take 900 ms instead of 40 ms. What would you do?

    coroutinescoroutine-dispatchers
  • 56

    Two requests are started with async, the first await throws, and the second request keeps running longer than expected. How would you structure this operation?

    asynccoroutines
  • 57

    A dashboard loads profile, recommendations, and alerts in parallel, but one failed optional panel cancels all three. How would you isolate the failure?

    alerting
  • 58

    A cold Flow wraps an HTTP call, and two UI collectors cause two identical requests every time the screen opens. How would you fix it?

    httpflow
  • 59

    A sensor emits 100 samples per second, processing each sample takes 15 ms, and every sample must reach storage. Would you use buffer, conflate, or collectLatest?

    concurrencyflow-operators
  • 60

    A stock ticker emits 20 prices per second, but the chart renders at 5 frames per second and only the newest complete price matters. Which Flow operator would you choose?

    flow
  • 61

    A validation Flow starts a 300 ms calculation for every keystroke, and stale calculations still update the form. Would you use buffer, conflate, or collectLatest?

    flowflow-operatorsvalidation
  • 62

    A search box sends queries for k, ko, and kot, and the slow k response overwrites the final results. How would you build the Flow pipeline?

    queriesci-cdflow
  • 63

    A MutableStateFlow holds a data class containing a mutable list; an item is changed in place, but collectors receive no update. Why, and what would you change?

    classes
  • 64

    A ViewModel emits a navigation command through MutableSharedFlow with replay 0 before the screen starts collecting, so navigation is lost. How would you handle it?

    viewmodel
  • 65

    A repository Flow converted with stateIn keeps GPS updates active after the last screen closes. Which sharing policy would you use and how would you test it?

    flow
  • 66

    A Compose list recomposes every row when an unrelated toolbar counter changes, and tracing shows frame time rising from 8 ms to 28 ms. How would you investigate?

  • 67

    A composable uses remember to format a price, but switching currency leaves the old formatted value on screen. What is the bug?

  • 68

    A profile screen calls LaunchedEffect(Unit) to load a user, but navigating from user 17 to user 42 in the same composition shows user 17. How would you fix it?

    oopcoroutinescompose-effects
  • 69

    Two copies of a reusable quantity picker each keep internal state, so the cart total and restored state drift from the selected quantity. How would you redesign it?

    iac
  • 70

    Two coroutines redeem the same coupon by reading unused and then updating it, and both orders receive the discount. How would you fix this with Room?

    roomcoroutines
  • 71

    A Room migration from version 6 to 7 passes on a fresh install but crashes for upgraded users because an index is missing. How would you catch and repair it?

    indexesmigrationsroom
  • 72

    A note is edited offline on a phone and online on a tablet, then sync silently overwrites one version. How would you contain the conflict?

  • 73

    A Ktor route calls a blocking JDBC driver directly, and 50 concurrent requests push p95 latency above three seconds. How would you fix it?

    ktorlatencyconcurrency
  • 74

    A Ktor endpoint returns plain 401 responses instead of the JSON error body from StatusPages after authentication was added. What would you inspect?

    authendpointsktor
  • 75

    You need a deterministic test for a Ktor POST route with JSON validation and a fake repository. How would you build it?

    validationktor
  • 76

    A Spring service method annotated with @Transactional calls another annotated method on the same instance, but the inner transaction settings are ignored. Why and how would you fix it?

    transactions
  • 77

    A Kotlin Spring service has @Transactional, but no transaction starts after a build configuration change. What Kotlin-specific proxy issue would you check?

    transactionsproxyconfig
  • 78

    A suspend Spring service loads an entity inside @Transactional, switches context for remote work, and later saves it, producing inconsistent transaction behavior. How would you redraw the boundary?

    transactionscoroutines
  • 79

    A JPA entity was changed to a Kotlin data class, and adding it to a Set triggers lazy loads while generated copy methods create detached-looking duplicates. What would you change?

    kotlinclasseslazy-loading
  • 80

    A Spring Data endpoint loads 100 orders and mapping each order's customer produces 101 SQL queries. How would you remove the N+1 issue?

    sqln+1queries
  • 81

    A backend adds a subtitle field, and an older Kotlin client fails to decode the otherwise compatible JSON response. How would you configure and test it?

    configkotlin
  • 82

    A new optional pageSize property has a default of 20, but decoding an old payload fails with a missing-field error. What would you inspect?

  • 83

    A KMP build fails because the common expect declaration returns Long, while the iOS actual implementation returns ULong. How would you repair and prevent the mismatch?

  • 84

    A commonMain repository imports an Android-only networking class, so the iOS source set no longer compiles. How would you restructure it?

  • 85

    A shared KMP presenter creates its own long-lived CoroutineScope; Android screens leak and iOS requests continue after a Swift view disappears. How would you assign ownership?

    ownershipcoroutines
  • 86

    After changing a KSP annotation, compilation still uses an old generated adapter until the build directory is deleted. How would you diagnose the stale output?

    kotlin-symbol-processing
  • 87

    You are migrating a Room and Moshi module from kapt to KSP, and generated types disappear only in release builds. How would you stage and verify the migration?

    migrationsroomdatabase-migrations
  • 88

    Gradle configuration cache is enabled, but a custom task reads an environment value through the Project object during execution. How would you fix it?

    configbuildcaching
  • 89

    A dependency upgrade increases an incremental assembleDebug from 35 seconds to 92 seconds. How would you locate the regression?

    dependencies
  • 90

    Six Android modules copy slightly different Compose, Kotlin, and test settings, and one module now ships with an outdated compiler option. What would you change?

    kotlin
  • 91

    A coroutine test uses delay and Thread.sleep, passes locally in 600 ms, and times out intermittently in CI. How would you rewrite it?

    concurrencycoroutines
  • 92

    A price-allocation function passes example tests but fails for rare rounding combinations. How would you add a Kotest property test?

    testing
  • 93

    Enabling a new Detekt rule creates 1,800 findings across an existing project, but new violations must stop immediately. How would you roll it out?

  • 94

    A Flow uses catch before a map operator, but parsing exceptions thrown by map still crash the collector. What would you change?

    error-handlingflow
  • 95

    A withTimeout(500) wrapper does not stop a legacy blocking socket call for five seconds. How would you contain it?

  • 96

    A Compose screen collects a StateFlow directly, and background updates continue while the app is stopped. What would you change?

    flow
  • 97

    A Room Flow updates in one process, but a widget hosted in another app process continues showing stale rows. How would you investigate?

    concurrencyflowroom
  • 98

    A Ktor StatusPages handler logs an exception but clients sometimes receive an empty 500 because the response had already started. How would you debug it?

    ktorerror-handling
  • 99

    A Spring controller maps a lazy JPA collection after the service transaction has ended and throws LazyInitializationException. How would you fix the boundary?

    transactions
  • 100

    Common KMP tests pass on JVM, but an iOS actual implementation of a date formatter produces a different day near midnight. How would you catch and fix it?

    jvm