Kotlin Developer interview questions
100 real questions with model answers and explanations for Junior Kotlin Developer candidates.
See a Kotlin Developer resume example →Practice with flashcards
Spaced repetition · Hunter Pass
Questions
The Kotlin compiler translates Kotlin source into JVM bytecode that the JVM can execute.
- kotlinc compiles source files into .class files, often packaged in a JAR.
- The JVM loads and runs that bytecode, so the application needs a compatible Java runtime.
- Kotlin declarations are mapped to JVM types and bytecode signatures that follow the Java ABI, which lets Kotlin and Java classes call each other.
Why interviewers ask this: The interviewer checks whether you can distinguish compilation from execution and explain why Kotlin interoperates with Java.
val creates a read-only reference, while var creates a reference that can be reassigned.
- Prefer val when the reference should keep pointing to the same value after initialization.
- val does not make the referenced object immutable, so a val MutableList can still change its contents.
- Use var only when reassignment represents real changing state, such as a counter in a loop.
Why interviewers ask this: A strong answer distinguishes reference reassignment from mutation of the referenced object.
I use type inference when the initializer makes the type obvious and an explicit type when it clarifies a contract.
- val count = 3 is clear because the compiler infers Int without hiding intent.
- Public properties and function return types benefit from explicit types because callers depend on that API.
- An explicit nullable or interface type, such as String? or List<String>, can prevent an implementation detail from becoming the contract.
Why interviewers ask this: The interviewer is evaluating whether you use inference for readability without making important API types accidental.
A block-bodied function uses braces and explicit returns, while an expression-bodied function returns the value after the equals sign.
- fun square(x: Int) = x * x is concise when the function is one expression.
- A block body suits multiple statements, early returns, or local variables.
- The compiler can infer an expression body's return type, but an explicit type is clearer for a public function.
Why interviewers ask this: The interviewer checks basic function syntax and whether you choose the readable form for the function's complexity.
Default values make selected arguments optional, and named arguments identify which parameters a call supplies.
- A declaration such as fun connect(timeout: Int = 1000) gives callers a standard timeout.
- connect(timeout = 3000) is clearer than an unexplained positional number.
- Named arguments let callers skip earlier defaulted parameters without adding several overloads.
Why interviewers ask this: The interviewer wants to see that you understand both the call syntax and its readability benefit.
if is an expression because it can produce a value that is assigned or returned.
- val label = if (score >= 60) "pass" else "fail" replaces a separate mutable variable.
- When a value is required, both branches must produce compatible result types.
- This style keeps simple decisions local and encourages val instead of later reassignment.
Why interviewers ask this: The interviewer checks whether you understand Kotlin's expression-oriented style rather than treating if only as control flow.
I use when when one value or a small set of conditions has several distinct branches.
- It can match constants, ranges, types, or arbitrary Boolean conditions.
- As an expression, when returns the selected branch value directly.
- A when over an enum or sealed class can be exhaustive, so the compiler flags a missing case.
Why interviewers ask this: A strong answer explains both readability and the compiler support available for finite cases.
Kotlin ranges describe sequences of ordered values that for loops can iterate over.
- 1..5 includes both endpoints, while 1 until 5 excludes 5.
- downTo iterates downward and step changes the increment.
- Use for (item in items) for values and withIndex when both an index and value are needed.
Why interviewers ask this: The interviewer checks practical command of common iteration syntax and range boundaries.
A primary constructor is declared in the class header, and val or var parameters become properties.
- class User(val name: String, var active: Boolean) stores both constructor arguments on each instance.
- A parameter without val or var is available during initialization but is not exposed as a property.
- Property access uses user.name syntax while Kotlin generates the required JVM accessors.
Why interviewers ask this: The interviewer is testing whether you can define a simple class without confusing constructor parameters with stored properties.
init blocks run common initialization for a new instance, while secondary constructors offer additional construction paths.
- init blocks execute in source order together with property initializers.
- When a class has a primary constructor, every secondary constructor must delegate to it directly or through another secondary constructor.
- Prefer default arguments or factory functions when they express the alternatives more clearly than many constructors.
Why interviewers ask this: The interviewer checks initialization order and the secondary-constructor delegation rule when a primary constructor exists.
An interface defines a capability a class can implement, while an abstract class can also hold shared instance state and constructor logic.
- A class can implement multiple interfaces but inherit from only one class.
- Interfaces may provide method bodies, but they do not have backing fields for stored instance state.
- Use an abstract class when related subclasses genuinely share state or protected implementation.
Why interviewers ask this: A strong answer chooses between the two based on multiple capabilities versus shared state and construction.
Kotlin uses public, internal, protected, and private to control where declarations are accessible.
- public is the default and exposes a declaration wherever its owner is visible.
- internal limits access to the same compilation module, such as one Gradle module.
- protected exposes class members to subclasses, while private limits access to the containing class or file.
Why interviewers ask this: The interviewer checks whether you know Kotlin's module-level internal visibility and the basic access boundaries.
An object declaration creates a named singleton whose instance is initialized on first access.
- It is useful for stateless utilities or one shared implementation, such as object JsonDefaults.
- Members are accessed through the object name without calling a constructor.
- Shared mutable state inside an object is still global state and needs careful synchronization and testing.
Why interviewers ask this: The interviewer wants the singleton behavior plus awareness that object does not make mutable state safe.
A companion object holds members associated with a class rather than with one particular instance.
- Call User.create() when create is declared in User's companion object.
- It commonly contains factory functions and constants that belong conceptually to the class.
- It is a real object and can implement an interface, not merely Java-style static syntax.
Why interviewers ask this: The interviewer checks whether you understand class-associated members and Kotlin's object-based implementation.
A data class generates value-oriented methods from properties in its primary constructor.
- It provides equals and hashCode based on those constructor properties.
- It also generates a readable toString, component functions, and copy.
- A data class suits records such as UserDto, but behavior and invariants can still be added when needed.
Why interviewers ask this: A strong answer names the generated behavior and explains the record-like use case without calling data classes plain containers.
copy creates a new data-class instance with selected constructor properties changed, and component functions support destructuring.
- user.copy(name = "Mina") keeps the other primary-constructor values unchanged.
- val (name, active) = user calls component1 and component2 in constructor-property order.
- copy is shallow, so nested mutable objects are still shared unless they are copied separately.
Why interviewers ask this: The interviewer checks practical data-class usage and whether you recognize that copy does not recursively clone an object graph.
I use an enum for a fixed set of singleton constants and a sealed class for a closed set of cases with different data.
- Direction.NORTH is one enum constant and every constant has the same declared shape.
- A sealed hierarchy can model Loading, Success(data), and Error(message) as different types.
- Both enable exhaustive when expressions when all known cases are handled.
Why interviewers ask this: The interviewer is evaluating whether you can model simple fixed values separately from state variants carrying data.
== checks structural equality, while === checks whether two references point to the same object.
- a == b calls a safe form of equals, including correct handling when a is null.
- a === b is true only for the identical object reference.
- Use == for values such as strings and data classes; reserve === for cases where identity itself matters.
Why interviewers ask this: A strong answer distinguishes value equality from object identity and uses each operator appropriately.
Read-only collection interfaces expose no mutation operations, while mutable interfaces add operations such as add and remove.
- listOf returns a List view and mutableListOf returns a MutableList.
- Accepting List in a function communicates that the function will not modify it through that reference.
- A read-only view is not a deep immutable guarantee because another mutable reference may still change the same collection.
Why interviewers ask this: The interviewer checks whether you avoid the common mistake of equating Kotlin's read-only interfaces with guaranteed immutability.
I choose the collection by how values are identified and whether order or duplicates matter.
- List keeps an ordered sequence and allows duplicate elements.
- Set represents unique elements and is useful for membership checks.
- Map stores values by unique keys, such as a user ID mapped to a User.
Why interviewers ask this: The interviewer wants a practical distinction among the three core collection types.
Locked questions
- 21
What do map and filter do to a collection?
- 22
What is fold used for?
- 23
How does Kotlin distinguish nullable and non-null types?
fundamentalskotlinnull-safety - 24
How do the safe-call and Elvis operators work together?
null-safety - 25
How can let help with a nullable value?
null-safety - 26
Why is the not-null assertion operator risky?
fundamentals - 27
What are type checks and smart casts in Kotlin?
kotlintype-casts - 28
What does the safe-cast operator do?
type-casts - 29
What is a lambda in Kotlin?
lambdakotlin - 30
What is a higher-order function?
functions - 31
What is an extension function?
extensions - 32
What problem do generic types solve?
generics - 33
How do you choose among Kotlin scope functions at a beginner level?
kotlinscope-functions - 34
How does try work as an expression in Kotlin?
kotlin - 35
What are Java platform types in Kotlin?
kotlinjava-interop - 36
What does suspend mean on a Kotlin function?
kotlincoroutines - 37
When should you use launch versus async?
asynccoroutines - 38
What are Job and delay in coroutine code?
coroutines - 39
What does structured concurrency mean?
concurrencystructured-concurrency - 40
What does it mean that a Flow is cold?
flow - 41
How do state and recomposition work in Jetpack Compose?
composecompose-recomposition - 42
What is an Android ViewModel for?
viewmodel - 43
What problem does Room solve on Android?
room - 44
What is Gradle responsible for in a Kotlin project?
buildkotlin - 45
How would you write a simple unit test for a Kotlin function?
unitunit-testingkotlin - 46
What are Ktor and Spring used for in Kotlin backend development?
ktorkotlin - 47
How do string templates work in Kotlin?
kotlin - 48
How can a Kotlin property customize reads and writes?
kotlin - 49
How does inheritance work when Kotlin classes are final by default?
oopkotlinownership - 50
What does Unit mean as a Kotlin return type?
kotlin - 51
A function receives name: String?, and name.length does not compile. How would you return the length while treating a missing name as empty?
- 52
A screen receives permissionGranted: Boolean?, where null means the permission has not been checked yet. How would you allow an action only when permission is explicitly granted?
fundamentals - 53
A Java method declared as String getToken() sometimes returns null, and Kotlin crashes on token.length even though no nullable warning appeared. How would you fix it?
fundamentalskotlinnull-safety - 54
An app crashes at user.email!!.lowercase() when an older account has no email. What local fix would you make?
- 55
A Fragment crashes with UninitializedPropertyAccessException when binding.title is accessed before onCreateView. How would you correct the lateinit usage?
android-fragments - 56
Code declares val total = 0 and then tries total += price inside a loop, causing Val cannot be reassigned. What is the smallest correct change?
- 57
A developer is surprised that val items = mutableListOf<String>() still allows items.add("A"). Is this a bug, and how would you make the contents read-only?
- 58
A function receives List<String> and fails to compile at names.add("Sam"). How would you fix the task without weakening the function contract?
- 59
Two distinct User instances with the same data compare false because code uses first === second. What should change?
distinct - 60
A test expects intArrayOf(1, 2) == intArrayOf(1, 2) to be true, but it fails. How would you compare the arrays?
- 61
A sealed UiState gains an Empty subtype and an existing when expression stops compiling. What should you do?
sealed-classes - 62
val updated = order.copy() is followed by updated.lines.add(item), and the original order.lines also changes. Why, and what local fix works?
- 63
A chain uses apply to calculate a String, but the inferred result is the original Builder; another uses let only to configure that Builder. How would you choose between let, apply, and run?
config - 64
An extension fun Animal.label() = "animal" still returns "animal" for a Dog stored in an Animal variable, and a similarly named member also ignores the extension. Why?
- 65
An API parser produces List<String?>, but renderNames expects List<String>. How would you resolve the type mismatch safely?
api - 66
A function tries to pass MutableList<Int> to a parameter of type MutableList<Number>, and Kotlin rejects it. What is the practical fix?
kotlin - 67
A Ktor query parameter page is parsed with page.toInt() and malformed input returns a 500 error. How would you parse it?
queriesktor - 68
A request contains sort=recent, but enumValueOf<Sort>(sort) crashes because enum constants are uppercase. How would you make parsing robust?
- 69
Code wraps a file parse in catch (e: Exception) and reports every failure as Invalid file, including cancellation and disk errors. How would you narrow it?
resilienceerror-handling - 70
A parser opens an inputStream and leaks it when decode throws. What Kotlin construct would you use?
kotlin - 71
A ViewModel starts async { repository.load() } only for its side effect and never uses the Deferred. Should it use launch or async?
asynccoroutinesviewmodel - 72
Two prices are loaded with val retail = async { api.retailPrice() } and val member = async { api.memberPrice() }, but the UI tries to subtract the Deferred values. What is missing?
asynccoroutinesapi - 73
An exception escapes viewModelScope.launch while the state is Loading. What can happen on Android, and how would you handle this local failure?
error-handlingcoroutinesviewmodel - 74
A button handler calls Thread.sleep(1000) before updating Compose UI, and the app freezes for a second. What should replace it?
concurrency - 75
A long-running coroutine catches Exception, logs it, and continues even after the user leaves the screen. What cancellation bug would you fix?
error-handlingcoroutinesresilience - 76
A Fragment creates CoroutineScope(Dispatchers.Main) and keeps collecting after its view is destroyed. What scope would you use?
coroutinescoroutine-dispatchersandroid-fragments - 77
A coroutine performs a large CPU loop with no suspending calls and ignores cancel() until the loop ends. How would you make it cooperative?
coroutines - 78
A repository function returns Flow<List<Item>>, but calling repository.items() produces no database query or UI update. What is missing?
databasequeriesflow - 79
A screen calls collect on the same cold network Flow in two separate launch blocks, and the request runs twice. How would you remove the duplicate work?
flowcoroutines - 80
A Flow emits all products, but the screen needs only in-stock product names. Which simple operators would you apply?
flow - 81
A Flow collection terminates on an IOException and the screen never receives an error state. Where would you handle the failure?
flow - 82
A Compose counter declares var count = 0 inside the composable, so every recomposition resets it. What is the local fix?
compose-recomposition - 83
A composable stores remember { mutableListOf<Item>() }; items.add(newItem) succeeds, but the list UI does not recompose. How would you change the state?
- 84
Rows in a LazyColumn keep the wrong checkbox state after an item is inserted at the top. What would you add?
compose-lists - 85
A composable launches the repository request directly and reloads on rotation. How would you move this state into a ViewModel?
viewmodelcoroutines - 86
Calling a Room DAO query from a click handler throws Cannot access database on the main thread. What is the correct fix?
databasequeriesroom - 87
A Room DAO returns List<Task>, so the screen never updates after another insert. What return type would make the query observable?
queriesroomdata-access - 88
After increasing a Room database from version 1 to 2 and adding a NOT NULL column, existing users crash with a missing migration. What should be added?
databasemigrationsschema - 89
A WorkManager upload returns Result.failure() whenever the device is offline, so it never runs again. What should transient network failure return?
background-work - 90
Hilt reports that PaymentRepository cannot be provided because it is an interface with no binding. What local DI declaration is missing?
dependency-injectiontypes - 91
A details destination expects an Int itemId, but navigation passes the string abc and crashes during argument conversion. How would you prevent this?
- 92
A Ktor POST route calls call.receive<CreateUser>() and malformed JSON becomes an unhelpful server error. How would you handle the request?
ktor - 93
A Ktor client calls response.body<User>() for every response and crashes when the server returns 404 with an error body. What should happen first?
ktor - 94
Gradle reports duplicate Kotlin runtime classes because kotlin-stdlib 2.x is resolved alongside an old kotlin-stdlib-jdk8 1.7 transitive dependency, whose classes were merged into kotlin-stdlib in 1.8. How would you investigate and fix it?
dependenciesbuildkotlin - 95
After resolving a Git merge conflict in build.gradle.kts, the build says an expected dependency is missing. What would you check before pushing the merge?
gitdependenciesbuild - 96
A unit test for a suspend function uses runBlocking and real delays, making the suite slow. What coroutine test setup would you use?
unitunit-testingcoroutine-testing - 97
A test launches collection of a finite Flow but asserts before any values arrive. How would you make this simple Flow test deterministic?
flowcoroutines - 98
A ViewModel test checks uiState immediately after starting a mocked load and still sees Loading. How would you structure the test?
viewmodel - 99
A repository catches an API exception and should return a failure result, but its unit test only covers success. What focused test would you add?
unitunit-testingapi - 100
A test of calculateDiscount passes only one normal price, and a bug appears for zero and negative input. How would you improve this basic unit test set?
unitunit-testing