Skip to content

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

kotlinjvm

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.

mutability

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.

kotlin

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.

kotlin

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

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.

kotlin

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.

typeskotlin

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.

csskotlin

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.

classes

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.

componentsclasses

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.

sealed-classes

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.

kotlin

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

kotlin

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