Preparing for a Kotlin interview? This guide covers the 50 most common questions asked in 2026.

Questions are grouped by difficulty. Each answer is short and clear. Code examples are included where they help.

Quick Reference — Key Kotlin Features

FeatureWhat It Does
Null safetyCompiler prevents null pointer exceptions
CoroutinesLightweight concurrency without threads
Extension functionsAdd functions to existing classes
Data classesAuto-generate equals, hashCode, toString, copy
Sealed classesRestricted class hierarchies
Smart castsAutomatic type casting after type checks
Scope functionslet, run, with, apply, also
Companion objectsStatic-like members in classes

Beginner Questions (1-20)

1. What is Kotlin?

Kotlin is a modern, statically-typed language developed by JetBrains. It runs on the JVM and compiles to JavaScript or native code. Google made it the preferred language for Android in 2019. It is 100% interoperable with Java.

2. What is the difference between val and var?

val is read-only (immutable reference). var is mutable and can be reassigned. Use val by default and only use var when you need to change the value.

val name = "Alex"   // cannot reassign
var age = 25        // can reassign
age = 26            // OK
// name = "Sam"     // ERROR

3. What is null safety in Kotlin?

Kotlin’s type system separates nullable and non-nullable types. A regular type like String cannot hold null. You need String? to allow null values. This prevents null pointer exceptions at compile time.

var name: String = "Alex"
// name = null  // ERROR — non-nullable type

var nickname: String? = "Al"
nickname = null  // OK — nullable type

4. What is the difference between == and === in Kotlin?

== checks structural equality (calls equals()). === checks referential equality (same object in memory). This is the opposite of Java where == checks reference.

val a = "hello"
val b = "hello"
println(a == b)   // true — same value
println(a === b)  // true — string pool optimization

5. What are data classes?

Data classes automatically generate equals(), hashCode(), toString(), copy(), and componentN() functions. You declare them with the data keyword. They must have at least one parameter in the primary constructor.

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

val user = User("Alex", 25)
println(user)  // User(name=Alex, age=25)
val copy = user.copy(age = 26)

6. What is the difference between List and MutableList?

List is read-only. You cannot add, remove, or modify elements. MutableList allows modifications. Kotlin separates read-only and mutable collection interfaces by design.

val readOnly: List<String> = listOf("a", "b")
val mutable: MutableList<String> = mutableListOf("a", "b")
mutable.add("c")  // OK
// readOnly.add("c")  // ERROR

7. What is string interpolation?

String interpolation lets you embed variables and expressions inside strings using $. Use $variable for simple values and ${expression} for complex expressions.

val name = "Alex"
println("Hello, $name!")           // Hello, Alex!
println("Length: ${name.length}")  // Length: 4

8. What is the when expression?

when is Kotlin’s replacement for the switch statement. It can match values, ranges, types, and conditions. It can be used as an expression that returns a value.

val x = 5
val result = when (x) {
    1 -> "one"
    in 2..5 -> "two to five"
    else -> "other"
}

9. What are Kotlin’s basic types?

Kotlin has these basic types: Int, Long, Float, Double, Boolean, Char, String, and Byte. Unlike Java, there are no primitive types in the source code. The compiler optimizes them to primitives when possible.

10. What is the Elvis operator?

The Elvis operator ?: returns the left side if it is not null, otherwise returns the right side. It is a concise way to provide default values for nullable expressions.

val name: String? = null
val displayName = name ?: "Unknown"  // "Unknown"

11. What is a companion object?

A companion object is an object declared inside a class using the companion keyword. It provides a way to define static-like members. There is exactly one companion object per class.

class Factory {
    companion object {
        fun create(): Factory = Factory()
    }
}
val instance = Factory.create()

12. What are extension functions?

Extension functions let you add new functions to existing classes without modifying them. They are resolved statically at compile time, not dynamically.

fun String.addExclamation(): String = "$this!"

println("Hello".addExclamation())  // Hello!

13. What is the difference between let, run, with, apply, and also?

These are scope functions. let transforms the object and returns the result. run executes a block and returns the result. apply configures the object and returns it. also performs side effects and returns the object. with is like run but takes the object as an argument.

val result = "hello".let { it.uppercase() }  // HELLO
val user = User().apply { name = "Alex" }    // returns User

14. What is type inference?

Type inference means the compiler figures out the type from the value. You do not need to write the type explicitly when the compiler can deduce it. This makes code shorter without losing type safety.

val name = "Alex"     // inferred as String
val age = 25          // inferred as Int
val pi = 3.14         // inferred as Double

15. What are named and default arguments?

Named arguments let you specify parameter names when calling a function. Default arguments provide default values so you can skip them. These features reduce the need for method overloading.

fun greet(name: String, greeting: String = "Hello") {
    println("$greeting, $name!")
}
greet("Alex")                      // Hello, Alex!
greet("Alex", greeting = "Hi")    // Hi, Alex!

16. What is the difference between object and class?

class creates a blueprint for objects. object creates a singleton — exactly one instance. Object declarations are thread-safe and initialized lazily on first access.

object Database {
    fun connect() { /* ... */ }
}
Database.connect()  // singleton, no instantiation needed

17. What are destructuring declarations?

Destructuring lets you unpack an object into multiple variables. It works with data classes, pairs, maps, and any class that has componentN() functions.

data class User(val name: String, val age: Int)
val (name, age) = User("Alex", 25)
println(name)  // Alex

18. What is the init block?

The init block runs when an instance is created. It executes after the primary constructor. You can have multiple init blocks and they run in order.

class User(val name: String) {
    init {
        println("User created: $name")
    }
}

19. What is the difference between open and final?

In Kotlin, classes and functions are final by default. You cannot inherit from them or override them unless you mark them as open. This is the opposite of Java.

open class Animal {
    open fun sound() = "..."
}
class Dog : Animal() {
    override fun sound() = "Woof"
}

20. What is a lambda in Kotlin?

A lambda is an anonymous function. It is defined inside curly braces. The last parameter of type function can be placed outside parentheses. Lambdas are used heavily with collection functions.

val numbers = listOf(1, 2, 3, 4, 5)
val even = numbers.filter { it % 2 == 0 }  // [2, 4]

Intermediate Questions (21-35)

21. What are coroutines?

Coroutines are Kotlin’s way of handling asynchronous programming. They are lightweight and do not block threads. You launch them with launch or async inside a coroutine scope. They use suspend functions to pause and resume execution.

suspend fun fetchData(): String {
    delay(1000)  // non-blocking delay
    return "Data loaded"
}

// Launch a coroutine
viewModelScope.launch {
    val data = fetchData()
    println(data)
}

22. What is the difference between launch and async?

launch starts a coroutine and returns a Job. It is “fire and forget.” async starts a coroutine and returns a Deferred<T>. You call await() to get the result. Use async when you need a return value.

val job = scope.launch { doWork() }         // no result
val deferred = scope.async { fetchData() }  // returns Deferred
val result = deferred.await()               // get the result

23. What are sealed classes?

Sealed classes restrict which classes can inherit from them. All subclasses must be defined in the same file (or same module since Kotlin 1.5). The compiler knows all possible subtypes, so when expressions do not need an else branch.

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

fun handle(result: Result) = when (result) {
    is Result.Success -> println(result.data)
    is Result.Error -> println(result.message)
    is Result.Loading -> println("Loading...")
}

24. What is smart casting?

Smart casting means the compiler automatically casts a variable after a type check. You do not need explicit casting. This works with is checks in if and when expressions.

fun describe(obj: Any): String = when (obj) {
    is String -> "String of length ${obj.length}"  // smart cast
    is Int -> "Integer: ${obj * 2}"                // smart cast
    else -> "Unknown"
}

25. What are higher-order functions?

Higher-order functions take functions as parameters or return functions. They are the foundation of Kotlin’s collection operations like map, filter, and fold.

fun operate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
    return operation(a, b)
}
val sum = operate(3, 4) { x, y -> x + y }  // 7

26. What is the difference between map, flatMap, and flatten?

map transforms each element and returns a list of results. flatMap transforms each element into a list and flattens the results into one list. flatten just merges a list of lists into one list.

val words = listOf("hello world", "hi there")
val letters = words.map { it.split(" ") }      // [[hello, world], [hi, there]]
val flat = words.flatMap { it.split(" ") }      // [hello, world, hi, there]

27. What is delegation in Kotlin?

Delegation lets a class delegate interface implementation to another object using the by keyword. This avoids inheritance and follows the “composition over inheritance” principle.

interface Printer {
    fun print(message: String)
}

class ConsolePrinter : Printer {
    override fun print(message: String) = println(message)
}

class App(printer: Printer) : Printer by printer

28. What are inline functions?

Inline functions replace the function call with the function body at compile time. This eliminates the overhead of lambda objects. Use inline for small functions that take lambdas as parameters.

inline fun measure(block: () -> Unit) {
    val start = System.currentTimeMillis()
    block()
    println("Time: ${System.currentTimeMillis() - start}ms")
}

29. What is the difference between Sequence and Iterable?

Iterable processes elements eagerly — each operation creates a new list. Sequence processes elements lazily — operations are chained and executed only when a terminal operation is called. Use sequences for large collections.

val result = (1..1_000_000).asSequence()
    .filter { it % 2 == 0 }
    .map { it * 2 }
    .take(10)
    .toList()

30. What are generics in Kotlin?

Generics let you write code that works with different types while keeping type safety. You declare type parameters with angle brackets. Kotlin supports both declaration-site and use-site variance.

class Box<T>(val value: T)

val stringBox = Box("Hello")
val intBox = Box(42)

31. What is the difference between in and out variance?

out means the type is a producer (covariant) — you can only read from it. in means the type is a consumer (contravariant) — you can only write to it. This is Kotlin’s equivalent of Java’s ? extends and ? super.

interface Source<out T> {
    fun next(): T  // can only return T
}

interface Sink<in T> {
    fun put(item: T)  // can only consume T
}

32. What is structured concurrency?

Structured concurrency means coroutines follow a parent-child hierarchy. When a parent scope is cancelled, all children are cancelled too. This prevents leaks and makes error handling predictable. viewModelScope and lifecycleScope in Android use this pattern.

33. What are Kotlin Flows?

Flow is Kotlin’s way of handling streams of data asynchronously. It is cold — it only runs when collected. It supports operators like map, filter, combine, and flatMapLatest. Use StateFlow and SharedFlow for state management.

fun numbers(): Flow<Int> = flow {
    for (i in 1..5) {
        delay(100)
        emit(i)
    }
}

numbers().collect { println(it) }

34. What is the crossinline keyword?

crossinline prevents non-local returns in inlined lambdas. When you pass a lambda to another execution context (like a different thread), you use crossinline to forbid return statements that would return from the enclosing function.

inline fun runOnThread(crossinline block: () -> Unit) {
    Thread { block() }.start()
}

35. What is a value class?

Value classes (formerly inline classes) wrap a single value without runtime overhead. The compiler replaces them with the wrapped type at runtime. They are useful for type-safe wrappers without allocation cost.

@JvmInline
value class Email(val value: String)

fun sendEmail(email: Email) { /* ... */ }
sendEmail(Email("alex@example.com"))

Advanced Questions (36-50)

36. What is reified in Kotlin?

reified preserves type information at runtime inside inline functions. Normally, generics are erased at runtime on the JVM. With reified, you can use is checks and access the class of the type parameter.

inline fun <reified T> isType(value: Any): Boolean {
    return value is T
}
println(isType<String>("hello"))  // true
println(isType<Int>("hello"))     // false

37. What is the difference between StateFlow and SharedFlow?

StateFlow always has a current value and emits only the latest value to new collectors. SharedFlow has no initial value and can replay a configurable number of emissions. Use StateFlow for UI state and SharedFlow for events.

val state = MutableStateFlow(0)       // always has a value
val events = MutableSharedFlow<String>()  // no initial value

38. What are context parameters?

Context parameters (experimental in Kotlin 2.0+) let you require multiple context types for a function. This is useful for dependency injection without parameter passing. They enable clean DSL-style code. Note: this feature is still experimental and the syntax may change.

context(Logger, Database)
fun saveUser(user: User) {
    log("Saving user")    // from Logger context
    insert(user)          // from Database context
}

39. How does Kotlin handle Java interop?

Kotlin is 100% interoperable with Java. You can call Java from Kotlin and vice versa. Kotlin uses annotations like @JvmStatic, @JvmField, @JvmOverloads, and @Throws to control how Kotlin code appears to Java callers.

40. What is the contracts API?

Contracts let you provide extra guarantees to the compiler. For example, you can tell the compiler that after a function returns true, a variable is not null. This enables better smart casting.

@OptIn(ExperimentalContracts::class)
fun String?.isNotNullOrEmpty(): Boolean {
    contract {
        returns(true) implies (this@isNotNullOrEmpty != null)
    }
    return this != null && isNotEmpty()
}

41. What is Kotlin Multiplatform (KMP)?

KMP lets you share Kotlin code across Android, iOS, web, and desktop. You write shared business logic once and use platform-specific code only where needed. It uses expect/actual declarations for platform differences. Learn more in our KMP tutorial series.

42. What is the difference between expect and actual?

expect declares an API in common code without implementation. actual provides the platform-specific implementation. This is how KMP handles platform differences like file access or database queries.

// Common code
expect fun platformName(): String

// Android
actual fun platformName(): String = "Android"

// iOS
actual fun platformName(): String = "iOS"

43. What is Channel in coroutines?

A Channel is a coroutine-based communication primitive. It allows one coroutine to send values and another to receive them. Channels can be buffered or unbuffered. They follow a producer-consumer pattern.

val channel = Channel<Int>()
launch { channel.send(42) }
launch { println(channel.receive()) }  // 42

44. What is the difference between suspendCoroutine and suspendCancellableCoroutine?

Both convert callback-based APIs to suspend functions. suspendCancellableCoroutine also supports cancellation — when the coroutine is cancelled, it invokes the invokeOnCancellation handler. Always prefer the cancellable version.

suspend fun fetchData(): String = suspendCancellableCoroutine { cont ->
    api.fetch(
        onSuccess = { cont.resume(it) },
        onError = { cont.resumeWithException(it) }
    )
    cont.invokeOnCancellation { api.cancel() }
}

45. What is the difference between object declaration and object expression?

Object declaration creates a named singleton. Object expression creates an anonymous object (like Java anonymous inner classes). Object expressions can implement interfaces and extend classes.

// Object declaration — singleton
object Logger { fun log(msg: String) {} }

// Object expression — anonymous
val listener = object : ClickListener {
    override fun onClick() { /* ... */ }
}

46. How do you handle exceptions in coroutines?

Use try-catch inside coroutines, or use a CoroutineExceptionHandler. For async, exceptions are thrown when you call await(). SupervisorJob prevents one child’s failure from cancelling siblings.

val handler = CoroutineExceptionHandler { _, e ->
    println("Caught: $e")
}
scope.launch(handler) {
    throw RuntimeException("Oops")
}

47. What are property delegates?

Property delegates let you reuse property logic. Kotlin provides built-in delegates: lazy, observable, and vetoable. You can also create custom delegates by implementing getValue() and setValue().

val expensiveValue by lazy {
    println("Computing...")
    calculateResult()
}

var name: String by Delegates.observable("Alex") { _, old, new ->
    println("Changed from $old to $new")
}

48. What is the Nothing type?

Nothing is a type with no instances. It represents a value that never exists. Functions that always throw exceptions or run forever return Nothing. It is a subtype of every other type.

fun fail(message: String): Nothing {
    throw IllegalArgumentException(message)
}

val result: String = name ?: fail("Name required")

49. What are annotation processors vs KSP?

Annotation processors (kapt) process annotations at compile time to generate code. KSP (Kotlin Symbol Processing) is the modern replacement. KSP is up to 2x faster because it works directly with Kotlin symbols instead of converting to Java stubs first.

50. What is the K2 compiler?

K2 is Kotlin’s new compiler frontend, stable since Kotlin 2.0. It is up to 2x faster than the old compiler. It enables new language features like context parameters (experimental), improved smart casts, and better IDE performance. All new Kotlin projects in 2026 use K2 by default.


Bonus Tips for Interviews

  1. Practice in the Kotlin Playground — Use play.kotlinlang.org to test code before your interview.

  2. Know coroutines deeply — Most companies ask about coroutines, structured concurrency, and Flows. Practice writing suspend functions and understanding dispatchers.

  3. Understand null safety trade-offs — Know when to use !!, ?., ?:, and let. Explain why !! is discouraged and what alternatives exist.

  4. Compare with Java — Be ready to explain how Kotlin improves on Java. Read our Kotlin vs Java 2026 comparison for key differences.

  5. Read the official documentation — The Kotlin docs are excellent. Focus on the “Concepts” and “Coroutines” sections.

  6. Build something — The best interview prep is building a real project. Follow our Kotlin tutorial series to practice hands-on.

  7. Review your code examples — Make sure every code snippet you show compiles and runs correctly.