Kotlin Interview Questions and Answers

Null safety, coroutines, extension functions and Java interop.

Practise 10 random 6 peer-reviewed questions
Kotlin Interview Syllabus & Preparation Strategy

Whether you are preparing for entry-level Kotlin interview questions for freshers or senior software engineer interview questions addressing concurrency, scalability, and system architecture, this track provides peer-reviewed model answers with syntax walkthroughs, edge cases, and practical interview tips.

1 What is a data class and what does it generate? Medium

A data class automatically generates equals(), hashCode(), toString(), copy() and componentN() functions for destructuring, based on the properties declared in its primary constructor.

data class User(val id: Int, val name: String)

val u = User(1, "Ada")
val renamed = u.copy(name = "Grace")
val (id, name) = renamed          // destructuring
println(renamed)                  // User(id=1, name=Grace)

Requirements: the primary constructor must have at least one parameter, all parameters must be val or var, and the class cannot be abstract, open, sealed or inner. Only primary-constructor properties participate in the generated methods; properties declared in the body are ignored.

Use data classes for value-like holders, DTOs and results. For persistence entities, generated equals and hashCode based on a mutable id can cause subtle bugs in sets and maps, so some teams use regular classes there.

2 What are Kotlin coroutines and when should you use them? Medium

Coroutines are Kotlin's solution for asynchronous and concurrent code. They are lightweight, suspendable computations: a suspend function can pause without blocking a thread, and the compiler turns it into a state machine.

suspend fun loadUser(id: Int): User = withContext(Dispatchers.IO) {
    api.fetch(id)          // runs on the IO dispatcher
}

fun main() = runBlocking {
    val user = loadUser(1)
    println(user)
}

Launching: launch for fire-and-forget work within a scope, async when you need a result, and coroutineScope or supervisorScope to structure failure. Dispatchers choose threads: Main for UI, IO for blocking calls, Default for CPU work. Because coroutines are cheap, you can start thousands of them.

Structured concurrency ties children to a parent scope, so they are cancelled and awaited together. Use coroutines for I/O and UI work, and as the modern replacement for callbacks and nested threads.

3 What are extension functions and what are their limitations? Medium

An extension function adds a method to a class without modifying or inheriting it. Resolution is static.

fun String.toTitleCase(): String =
    split(" ").joinToString(" ") { word ->
        word.replaceFirstChar { it.uppercase() }
    }

"hello world".toTitleCase()   // "Hello World"

Extensions are syntactic sugar for a static function whose first parameter is the receiver. They do not truly add members, which has consequences:

  • They cannot access private or protected members.
  • They are not virtual: dispatch is based on the declared static type, not the runtime type, so an extension can be shadowed unexpectedly.
  • A real member with the same signature always wins.
  • Extension properties cannot store state because there is no backing field.

Common uses are utility functions, Android view helpers and DSL builders like buildString and apply. Keep them in dedicated files so they do not become a dumping ground.

4 When would you use a sealed class instead of an enum? Medium

An enum class defines a fixed set of constants, each a single instance. It can have properties and methods, but every constant shares the same shape.

A sealed class or sealed interface restricts subclasses to the same package and module, giving a closed hierarchy where each subclass can hold different data.

sealed class Result {
    data class Success(val data: String) : Result()
    data class Error(val code: Int) : Result()
    object Loading : Result()
}

fun render(r: Result) = when (r) {
    is Result.Success -> r.data
    is Result.Error -> "error ${r.code}"
    Result.Loading -> "loading"
}

Because the hierarchy is closed, when is exhaustive and the compiler warns about a missing branch when used as an expression. Use enums for simple named values, and sealed types for state machines, API responses and algebraic data types where each case carries different data.

5 How do higher-order functions and inline functions work? Medium

A higher-order function takes a function as a parameter or returns one. Functions are first-class values with types like (Int) -> Int.

fun <T> List<T>.myFilter(predicate: (T) -> Boolean): List<T> {
    val out = mutableListOf<T>()
    for (item in this) if (predicate(item)) out.add(item)
    return out
}

val evens = (1..10).myFilter { it % 2 == 0 }

Lambdas passed to such functions are objects, so each call allocates and adds an indirect call. Marking the function inline substitutes its body at the call site, eliminating the lambda object and enabling non-local returns, where a return inside the lambda returns from the enclosing function.

inline fun measure(block: () -> Unit) {
    val start = System.nanoTime()
    block()
    println(System.nanoTime() - start)
}

Standard functions like let, run and apply are inline. Avoid inlining large functions or functions that store their lambda parameters.

6 Compare the scope functions let, run, with, apply and also. Medium

All five run a block with an object in context. They differ in how the object is referenced (it or this) and what they return (the object or the lambda result).

  • let: it, returns the lambda result. Used for null checks and transformations.
  • run: this, returns the result. Configuration plus computation.
  • with: this, returns the result. Grouping several calls on one object.
  • apply: this, returns the object. Object configuration.
  • also: it, returns the object. Side effects such as logging.
val user = User().apply {
    name = "Ada"
    age = 36
}

val upper = user.let { it.name.uppercase() }

Choose based on chaining needs rather than habit: use apply at the end of a builder chain, also for side effects, and let when you need the result. Avoid nesting them so deeply that this becomes ambiguous, and keep blocks short.

Frequently Asked Questions About Kotlin Interviews

What do hiring managers evaluate in Kotlin technical rounds?

Technical interviewers look for foundational fluency, idiomatic syntax, clarity when communicating complex logic, and awareness of performance trade-offs (e.g. memory footprint, render performance, and network latency) in production environments.

What are the best interview tips for practicing Kotlin questions?

Use active recall: summarize each answer in your own words before revealing the model solution. Focus on explaining why a certain approach is chosen rather than just memorizing code syntax.