Kotlin interviews test your understanding of null safety, coroutines, functional idioms, object-oriented design, and Android-specific patterns. This guide covers 50 common questions — with concise answers and code examples.
Quick reference
| Topic | Most asked questions |
|---|---|
| Basics | val/var, types, string templates, null safety |
| Functions | Default args, extension functions, lambdas, infix |
| Classes | Data classes, sealed classes, object/companion, delegation |
| Coroutines | suspend, CoroutineScope, launch/async, Flow |
| Collections | map/filter/reduce, sequences, destructuring |
| Android | ViewModel, StateFlow, Compose, Room |
| Advanced | Generics, reified, inline, DSLs |
Basics
1. What is the difference between val and var?
val name = "Alice" // read-only (immutable reference)
var count = 0 // mutable
count = 1 // OK
name = "Bob" // compile error
val is equivalent to Java's final. The underlying object can still be mutable (e.g., a val list can have items added if it's a MutableList).
2. What is Kotlin's type system and how does it handle null?
Kotlin distinguishes between nullable and non-nullable types at the type level:
var name: String = "Alice" // non-nullable — cannot be null
var nick: String? = null // nullable — can be null
name.length // safe
nick.length // compile error — must handle null
nick?.length // safe call — returns null if nick is null
nick!!.length // non-null assertion — throws NPE if null
nick?.length ?: 0 // Elvis operator — default value if null
| Operator | Meaning |
|---|---|
?. |
Safe call — returns null if receiver is null |
?: |
Elvis — provides default when left side is null |
!! |
Non-null assertion — throws NullPointerException |
as? |
Safe cast — returns null on failure |
3. What are string templates in Kotlin?
val name = "Alice"
val age = 30
println("Hello, $name!") // simple variable
println("She is ${age + 1} next year") // expression
println("Name length: ${name.length}") // property access
Multi-line strings use triple quotes and trimIndent():
val text = """
Hello, $name.
Age: $age.
""".trimIndent()
4. What is the difference between == and === in Kotlin?
val a = "hello"
val b = "hello"
val c = a
a == b // true — structural equality (calls equals())
a === b // true in this case (string pool), but not guaranteed
a === c // true — same reference
val x = Integer(1)
val y = Integer(1)
x == y // true
x === y // false — different objects
| Operator | Java equivalent | Meaning |
|---|---|---|
== |
.equals() |
Structural (value) equality |
=== |
== |
Referential equality |
5. What are Kotlin's basic types?
| Category | Types |
|---|---|
| Integer | Byte, Short, Int, Long |
| Floating | Float, Double |
| Character | Char |
| Boolean | Boolean |
| String | String |
| Arrays | Array<T>, IntArray, LongArray, etc. |
Kotlin has no primitive types in the source code — the compiler maps them to JVM primitives automatically. Use UInt, ULong, etc. for unsigned types.
Functions
6. What are default and named parameters?
fun greet(name: String, greeting: String = "Hello") {
println("$greeting, $name!")
}
greet("Alice") // Hello, Alice!
greet("Bob", greeting = "Hi") // Hi, Bob!
greet(greeting = "Hey", name = "Carol") // named — order doesn't matter
Named parameters make calls self-documenting and eliminate overload chains.
7. What are extension functions?
Extension functions add behaviour to existing classes without inheriting or modifying them:
fun String.isPalindrome(): Boolean {
val clean = this.lowercase().filter { it.isLetterOrDigit() }
return clean == clean.reversed()
}
"racecar".isPalindrome() // true
"hello".isPalindrome() // false
They are resolved statically (at compile time), not dynamically. They cannot override member functions.
// Useful standard library examples
listOf(1, 2, 3).sumOf { it * 2 } // 12
"hello world".split(" ") // [hello, world]
42.coerceIn(1, 100) // 42
8. What is the difference between a lambda and an anonymous function?
// Lambda
val multiply = { x: Int, y: Int -> x * y }
// Anonymous function — can use return to exit itself
val divide = fun(x: Int, y: Int): Int {
if (y == 0) return 0 // returns from anonymous function
return x / y
}
In a lambda, return returns from the enclosing function (non-local return). Use anonymous functions or return@label for local returns:
listOf(1, 2, 3).forEach { n ->
if (n == 2) return@forEach // continues to next iteration
println(n)
}
9. What are higher-order functions?
Functions that take functions as parameters or return functions:
fun <T> List<T>.filter(predicate: (T) -> Boolean): List<T> {
val result = mutableListOf<T>()
for (item in this) if (predicate(item)) result.add(item)
return result
}
val evens = listOf(1, 2, 3, 4).filter { it % 2 == 0 } // [2, 4]
Common Kotlin standard library higher-order functions:
| Function | Purpose |
|---|---|
map |
Transform each element |
filter |
Keep elements matching predicate |
reduce / fold |
Accumulate to single value |
flatMap |
Map then flatten |
groupBy |
Group into map by key |
sortedBy |
Sort by selector |
let, run, apply, also, with |
Scope functions |
10. What are Kotlin's scope functions?
| Function | Context object | Return value | Use case |
|---|---|---|---|
let |
it |
Lambda result | Null checks, transforms |
run |
this |
Lambda result | Configuration + compute result |
apply |
this |
Context object | Object configuration |
also |
it |
Context object | Side effects (logging) |
with |
this |
Lambda result | Group calls on an object |
val user = User().apply {
name = "Alice"
age = 30
}
val result = user.run {
"$name is $age years old"
}
user.let { u ->
println(u.name)
}
Classes
11. What is a data class?
data class Point(val x: Int, val y: Int)
val p1 = Point(1, 2)
val p2 = Point(1, 2)
p1 == p2 // true — structural equality
p1.toString() // Point(x=1, y=2)
val p3 = p1.copy(y = 5) // Point(x=1, y=5)
val (x, y) = p1 // destructuring
The compiler generates equals(), hashCode(), toString(), copy(), and componentN() functions automatically. Data classes must have at least one val/var primary constructor parameter.
12. What are sealed classes and when do you use them?
Sealed classes restrict the class hierarchy to a fixed set of subclasses, all defined in the same file:
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val message: String) : Result<Nothing>()
object Loading : Result<Nothing>()
}
fun handle(result: Result<User>) = when (result) {
is Result.Success -> showUser(result.data)
is Result.Error -> showError(result.message)
Result.Loading -> showSpinner()
// No else needed — compiler knows all subclasses
}
When to use: representing a finite set of states (network results, UI states, parser outputs).
13. What is the difference between object, companion object, and class?
// object — singleton, created lazily on first access
object Config {
val timeout = 5000
}
// companion object — static-like members on a class
class User(val name: String) {
companion object {
fun create(name: String) = User(name) // factory
}
}
val user = User.create("Alice")
// class — regular class, instantiated with constructor
class Counter {
var count = 0
}
| Concept | Instance | Purpose |
|---|---|---|
object |
Single (singleton) | Utility, singletons |
companion object |
One per class | Factory methods, constants |
class |
Multiple | General use |
14. What are interfaces in Kotlin? How do they differ from abstract classes?
interface Drawable {
fun draw() // abstract
fun describe() = "I am drawable" // default implementation
}
abstract class Shape {
abstract fun area(): Double
fun name() = "Shape" // concrete method
}
class Circle(val r: Double) : Shape(), Drawable {
override fun area() = Math.PI * r * r
override fun draw() = println("Drawing circle")
}
| Feature | Interface | Abstract class |
|---|---|---|
| Multiple inheritance | Yes (multiple interfaces) | No (single class) |
| Constructor | No | Yes |
| State (fields) | Only abstract/val with backing | Yes |
| Default methods | Yes | Yes |
15. What is class delegation with by?
interface Logger {
fun log(msg: String)
}
class ConsoleLogger : Logger {
override fun log(msg: String) = println(msg)
}
// Delegates all Logger methods to the provided instance
class Service(logger: Logger) : Logger by logger {
fun doWork() {
log("Working...") // delegated to logger
}
}
val service = Service(ConsoleLogger())
service.doWork() // Working...
The by keyword implements the Delegation pattern automatically — no boilerplate forwarding methods needed.
16. What are open, final, and abstract modifiers?
In Kotlin, all classes and functions are final by default (unlike Java):
open class Base {
open fun greet() = "Hello" // can be overridden
fun fixed() = "Cannot override"
}
class Child : Base() {
override fun greet() = "Hi" // OK
// override fun fixed() = ... // compile error
}
abstract class Shape {
abstract fun area(): Double // must be overridden
}
17. What is a data object (Kotlin 1.9+)?
data object Singleton {
val value = 42
}
println(Singleton) // Singleton (not Singleton@1a2b3c)
Singleton == Singleton // true
Like object but generates toString() and meaningful equals()/hashCode() — useful for sealed class singletons.
Coroutines
18. What is a coroutine?
A coroutine is a suspendable computation — it can pause execution (at suspend points) without blocking a thread, then resume later (potentially on a different thread).
suspend fun fetchUser(id: Int): User {
delay(1000) // suspends — releases the thread
return api.getUser(id)
}
Benefits over threads:
- Lightweight (thousands of coroutines on a few threads)
- Structured concurrency (scoped lifetime)
- Sequential code style for async operations
- Built-in cancellation
19. What is the difference between launch and async?
// launch — fire-and-forget, returns Job
val job = CoroutineScope(Dispatchers.IO).launch {
doWork()
}
job.cancel() // can cancel
// async — returns Deferred<T>, use await() to get result
val deferred = CoroutineScope(Dispatchers.IO).async {
fetchData()
}
val result = deferred.await() // suspends until done
launch |
async |
|
|---|---|---|
| Returns | Job |
Deferred<T> |
| Result | None | await() returns T |
| Exception | Propagates to scope | Held until await() |
| Use case | Side effects | Parallel computation |
20. What are Kotlin coroutine Dispatchers?
| Dispatcher | Thread pool | Use case |
|---|---|---|
Dispatchers.Main |
Main thread | UI updates (Android) |
Dispatchers.IO |
Elastic pool (64+) | Network, disk I/O |
Dispatchers.Default |
CPU cores | CPU-intensive work |
Dispatchers.Unconfined |
Caller's thread | Testing, edge cases |
withContext(Dispatchers.IO) {
val data = api.fetch() // runs on IO thread
withContext(Dispatchers.Main) {
updateUI(data) // switches to main thread
}
}
21. What is structured concurrency?
Coroutines are launched inside a CoroutineScope. When the scope is cancelled, all child coroutines are cancelled:
// viewModelScope cancels when ViewModel is destroyed
viewModelScope.launch {
val users = async { fetchUsers() }
val orders = async { fetchOrders() }
show(users.await(), orders.await()) // parallel
}
Rules:
- A parent waits for all children before completing
- Cancelling a parent cancels all children
- A child's exception propagates to the parent
22. What is Flow in Kotlin?
Flow is a cold, asynchronous data stream — it only produces values when collected:
fun numbers(): Flow<Int> = flow {
for (i in 1..5) {
delay(100)
emit(i)
}
}
// Collect
viewModelScope.launch {
numbers()
.filter { it % 2 == 0 }
.map { it * it }
.collect { println(it) } // 4, 16
}
Flow |
StateFlow |
SharedFlow |
|
|---|---|---|---|
| Cold/Hot | Cold | Hot | Hot |
| Replay | None | Last value | Configurable |
| Use case | One-shot streams | UI state | Events |
23. What is the difference between StateFlow and LiveData?
| Feature | StateFlow |
LiveData |
|---|---|---|
| Framework | Kotlin Coroutines | Android Jetpack |
| Lifecycle aware | Manual (repeatOnLifecycle) |
Built-in |
| Initial value | Required | Optional |
| Thread safety | Any thread, UI update via collect |
postValue for background |
| Compose support | Native | Needs .observeAsState() |
| Nullability | Non-null | Nullable |
// ViewModel
private val _uiState = MutableStateFlow(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
// Fragment
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state -> render(state) }
}
}
24. How do you handle exceptions in coroutines?
// SupervisorJob — sibling failures don't cancel each other
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
scope.launch {
try {
val result = fetchData()
} catch (e: IOException) {
showError(e.message)
}
}
// CoroutineExceptionHandler for unhandled exceptions
val handler = CoroutineExceptionHandler { _, exception ->
Log.e("App", "Caught: $exception")
}
CoroutineScope(handler + Dispatchers.IO).launch {
throw RuntimeException("Oops")
}
Note: CoroutineExceptionHandler only catches exceptions in launch, not async (those surface at await()).
Collections
25. What is the difference between List and MutableList?
val immutable: List<Int> = listOf(1, 2, 3)
val mutable: MutableList<Int> = mutableListOf(1, 2, 3)
mutable.add(4) // OK
// immutable.add(4) // compile error
// Kotlin collection interfaces
// List ← MutableList
// Set ← MutableSet
// Map ← MutableMap
Read-only views: listOf, setOf, mapOf. Mutable: mutableListOf, etc. For performance-critical code use ArrayList, HashMap directly.
26. What are sequences and when do you use them?
// Eager evaluation — creates intermediate lists
listOf(1..1_000_000)
.map { it * 2 }
.filter { it % 3 == 0 }
.take(10)
.toList()
// Lazy evaluation — no intermediate lists
(1..1_000_000).asSequence()
.map { it * 2 }
.filter { it % 3 == 0 }
.take(10)
.toList() // only processes what's needed
Use sequences when:
- The collection is large
- You chain multiple operations
- You use
take(n)/first()(early termination)
Use regular collections when the list is small — sequence overhead isn't worth it.
27. What is destructuring in Kotlin?
// Data class
data class Point(val x: Int, val y: Int)
val (x, y) = Point(3, 4)
// List / Array
val (first, second, *rest) = listOf(1, 2, 3, 4, 5) // spread not supported in destructuring
val list = listOf(1, 2, 3)
val (a, b, c) = list // OK for exactly 3 elements
// Map entry
for ((key, value) in mapOf("a" to 1, "b" to 2)) {
println("$key -> $value")
}
// Pair / Triple
val pair = "Alice" to 30
val (name, age) = pair
Works via componentN() operator functions generated by data class or defined manually.
Android-specific
28. What is a ViewModel and why use it in Android?
class UserViewModel(private val repo: UserRepository) : ViewModel() {
private val _users = MutableStateFlow<List<User>>(emptyList())
val users: StateFlow<List<User>> = _users.asStateFlow()
init { loadUsers() }
private fun loadUsers() {
viewModelScope.launch {
_users.value = repo.getUsers()
}
}
}
ViewModel:
- Survives configuration changes (screen rotation)
- Separates UI state from UI logic
- Scoped to Activity/Fragment lifecycle
- Use
viewModelScopefor coroutines — auto-cancelled ononCleared()
29. What is Jetpack Compose and how does state work?
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Column {
Text("Count: $count")
Button(onClick = { count++ }) {
Text("Increment")
}
}
}
| Property wrapper | Purpose |
|---|---|
remember |
Survives recomposition, lost on config change |
rememberSaveable |
Survives config change (saves to Bundle) |
mutableStateOf |
Observable state — triggers recomposition |
collectAsStateWithLifecycle |
Collects StateFlow safely |
State should flow down (via parameters), events flow up (via callbacks) — "state hoisting" pattern.
30. What is Room and how does it work with coroutines?
@Entity
data class User(
@PrimaryKey val id: Int,
val name: String
)
@Dao
interface UserDao {
@Query("SELECT * FROM user")
fun getAll(): Flow<List<User>> // reactive — emits on DB change
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(user: User)
@Delete
suspend fun delete(user: User)
}
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
Room generates SQL at compile time, validates queries, and integrates with coroutines (suspend functions) and Flow.
Advanced
31. What are generics and type variance in Kotlin?
// Invariant — only exact type
class Box<T>(val value: T)
// Covariant (out) — producer, T can be supertype
class ReadOnly<out T>(val value: T)
fun printAnimal(box: ReadOnly<Animal>) { ... }
val cat: ReadOnly<Cat> = ReadOnly(Cat())
printAnimal(cat) // OK — Cat is a subtype of Animal
// Contravariant (in) — consumer, T can be subtype
class Writer<in T> {
fun write(item: T) { ... }
}
val animalWriter: Writer<Animal> = Writer()
val catWriter: Writer<Cat> = animalWriter // OK
| Variance | Keyword | Can read | Can write | Use case |
|---|---|---|---|---|
| Covariant | out |
Yes | No | Producers (Source) |
| Contravariant | in |
No | Yes | Consumers (Sink) |
| Invariant | none | Yes | Yes | Read + write |
32. What is reified and inline?
// Without reified — T is erased at runtime
fun <T> List<Any>.filterIsInstance(): List<T> { /* T not available */ }
// With inline + reified — T is available at runtime
inline fun <reified T> List<Any>.filterIsInstance(): List<T> {
return this.filter { it is T }.map { it as T }
}
val list: List<Any> = listOf(1, "hello", 2, "world")
val strings: List<String> = list.filterIsInstance<String>() // [hello, world]
inline copies the function body at the call site. reified makes the type parameter available at runtime (bypassing JVM type erasure). Only works with inline functions.
33. What is a Kotlin DSL?
Kotlin's extension functions + lambdas with receivers enable clean DSL syntax:
// HTML DSL example
html {
head { title { +"Page Title" } }
body {
h1 { +"Hello, World!" }
p { +"This is Kotlin DSL" }
}
}
// Gradle Kotlin DSL
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
testImplementation(kotlin("test"))
}
Common Kotlin DSLs: Ktor routing, Kotlin HTML, Gradle build scripts, Exposed ORM, Anko.
34. What is delegation with by lazy?
class HeavyObject {
val expensive: String by lazy {
println("Computing...")
"expensive result"
}
}
val obj = HeavyObject()
println(obj.expensive) // Computing... → expensive result
println(obj.expensive) // expensive result (cached)
by lazy initializes on first access and caches the result. Thread-safe by default (LazyThreadSafetyMode.SYNCHRONIZED). For single-thread use: by lazy(LazyThreadSafetyMode.NONE).
35. What are operator functions?
data class Vector(val x: Int, val y: Int) {
operator fun plus(other: Vector) = Vector(x + other.x, y + other.y)
operator fun times(scale: Int) = Vector(x * scale, y * scale)
operator fun component1() = x
operator fun component2() = y
}
val v1 = Vector(1, 2)
val v2 = Vector(3, 4)
val v3 = v1 + v2 // Vector(4, 6)
val v4 = v1 * 3 // Vector(3, 6)
val (x, y) = v3 // destructuring via component1/2
Common operator functions: plus, minus, times, div, rem, unaryMinus, inc, dec, get, set, contains, invoke.
Kotlin vs Java
36. What does Kotlin eliminate from Java?
| Java pain point | Kotlin solution |
|---|---|
| NullPointerException | Nullable types (?), Elvis, safe call |
| Checked exceptions | No checked exceptions |
| Verbose getters/setters | Properties with backing field |
| Boilerplate POJOs | data class |
| Static methods | companion object, top-level functions |
| Anonymous classes | Lambda expressions |
instanceof + cast |
Smart cast |
| Builders | Named parameters + defaults |
37. How does Kotlin interop with Java?
// Calling Java from Kotlin — seamless
val list: java.util.ArrayList<String> = ArrayList()
list.add("hello")
// Platform types — Java types without null annotation
val javaString: String! = JavaClass.getString() // could be null
// Annotations for Java interop
class MyClass {
@JvmStatic fun staticMethod() = "static"
@JvmField val field = 42
@JvmOverloads fun greet(name: String, greeting: String = "Hello") = "$greeting, $name"
}
| Annotation | Purpose |
|---|---|
@JvmStatic |
Generates static method on companion object |
@JvmField |
Exposes property as public field (no getter/setter) |
@JvmOverloads |
Generates overloaded methods for default params |
@Throws |
Declares checked exceptions for Java callers |
Testing
38. How do you write unit tests in Kotlin?
// JUnit 5 + MockK
class UserServiceTest {
private val repo = mockk<UserRepository>()
private val service = UserService(repo)
@Test
fun `getUser returns user when found`() {
every { repo.findById(1) } returns User(1, "Alice")
val result = service.getUser(1)
assertEquals("Alice", result.name)
verify { repo.findById(1) }
}
@Test
fun `getUser throws when not found`() {
every { repo.findById(99) } returns null
assertThrows<UserNotFoundException> {
service.getUser(99)
}
}
}
39. How do you test coroutines?
@Test
fun `fetchData returns result`() = runTest {
val repo = mockk<DataRepository>()
coEvery { repo.fetchData() } returns "data"
val viewModel = MyViewModel(repo)
viewModel.load()
assertEquals("data", viewModel.state.value.data)
}
// Testing Flow
@Test
fun `flow emits correct values`() = runTest {
val flow = flowOf(1, 2, 3)
val results = flow.toList()
assertEquals(listOf(1, 2, 3), results)
}
Use runTest from kotlinx-coroutines-test — it replaces delay with virtual time and runs synchronously.
Common scenarios
40. How do you parse JSON with Kotlin?
// kotlinx.serialization (recommended)
@Serializable
data class User(val id: Int, val name: String)
val json = Json { ignoreUnknownKeys = true }
val user = json.decodeFromString<User>("{\"id\":1,\"name\":\"Alice\"}")
val string = json.encodeToString(user)
// Gson
val gson = Gson()
val user = gson.fromJson(jsonString, User::class.java)
// Moshi
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
val adapter = moshi.adapter(User::class.java)
val user = adapter.fromJson(jsonString)
41. How do you make network requests in Kotlin (Android)?
// Retrofit + coroutines
interface ApiService {
@GET("users/{id}")
suspend fun getUser(@Path("id") id: Int): User
}
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val service = retrofit.create(ApiService::class.java)
viewModelScope.launch {
try {
val user = service.getUser(1)
_uiState.value = UiState.Success(user)
} catch (e: HttpException) {
_uiState.value = UiState.Error(e.message())
}
}
42. How do you implement dependency injection in Kotlin?
// Hilt (Android) — most common
@HiltAndroidApp
class MyApp : Application()
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides @Singleton
fun provideRetrofit(): Retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.build()
}
@HiltViewModel
class UserViewModel @Inject constructor(
private val repo: UserRepository
) : ViewModel()
@AndroidEntryPoint
class MainActivity : AppCompatActivity()
Alternatives: Koin (simpler, no code generation) and Dagger 2 (Hilt is built on Dagger).
Enums & when
43. What are Kotlin enums?
enum class Direction(val degrees: Int) {
NORTH(0), EAST(90), SOUTH(180), WEST(270);
fun opposite() = when (this) {
NORTH -> SOUTH
SOUTH -> NORTH
EAST -> WEST
WEST -> EAST
}
}
Direction.NORTH.degrees // 0
Direction.NORTH.opposite() // SOUTH
Direction.values() // array of all
Direction.valueOf("EAST") // Direction.EAST
44. What is the when expression?
// Replaces switch — always exhaustive with sealed/enum
val result = when (x) {
is String -> "String of length ${x.length}"
is Int -> "Int: $x"
null -> "null"
else -> "Unknown"
}
// No argument — acts like if-else chain
val grade = when {
score >= 90 -> "A"
score >= 80 -> "B"
score >= 70 -> "C"
else -> "F"
}
// Multiple conditions on same branch
when (day) {
"Monday", "Tuesday", "Wednesday", "Thursday", "Friday" -> "Weekday"
"Saturday", "Sunday" -> "Weekend"
}
Error handling
45. How does Kotlin handle exceptions?
// All exceptions are unchecked
fun readFile(path: String): String {
return try {
File(path).readText()
} catch (e: FileNotFoundException) {
""
} catch (e: IOException) {
throw RuntimeException("Failed to read $path", e)
} finally {
println("Done")
}
}
// Result type — functional error handling
fun divide(a: Int, b: Int): Result<Int> = runCatching {
if (b == 0) throw ArithmeticException("Division by zero")
a / b
}
divide(10, 2).onSuccess { println(it) }
.onFailure { println(it.message) }
46. What is the runCatching function?
val result = runCatching {
apiCall()
}
// Fold — handle both cases
result.fold(
onSuccess = { data -> showData(data) },
onFailure = { error -> showError(error) }
)
// Map — transform the success value
val transformed = result
.map { it.toUpperCase() }
.getOrDefault("fallback")
// recover — handle failure with fallback
val safe = result.recover { e ->
when (e) {
is NetworkException -> "offline"
else -> throw e
}
}
Miscellaneous
47. What are type aliases?
typealias UserId = Int
typealias UserMap = Map<UserId, String>
typealias Callback<T> = (Result<T>) -> Unit
fun fetchUser(id: UserId, callback: Callback<User>) { ... }
// Makes code more readable, no runtime overhead
48. What is const val vs val?
const val MAX_SIZE = 100 // compile-time constant
val runtimeVal = computeSize() // runtime constant
// const val restrictions:
// - Must be top-level or in companion object / object
// - Must be String or primitive type
// - Value known at compile time
object Config {
const val TIMEOUT = 5000
val name = "App" // val — initialized at runtime
}
49. What is @JvmInline value class?
@JvmInline
value class UserId(val id: Int)
fun findUser(id: UserId): User? { ... }
findUser(UserId(42)) // type-safe, no boxing at runtime
// vs plain Int — no type confusion
fun badFind(id: Int) { ... }
badFind(42) // could accidentally pass any Int
Value classes wrap a single value with type safety. At runtime, they are typically unboxed (inlined), giving zero overhead compared to the raw type.
50. What are the most common Kotlin anti-patterns?
| Anti-pattern | Problem | Fix |
|---|---|---|
Overusing !! |
Runtime NPE | Use ?., ?:, requireNotNull |
| Mutable shared state with coroutines | Race conditions | Use StateFlow, Channel, or Mutex |
| Java-style Kotlin (static utils class) | Not idiomatic | Top-level functions, extension functions |
Blocking in coroutine (Thread.sleep) |
Blocks thread | Use delay() |
GlobalScope.launch |
Lifecycle leaks | Use viewModelScope, lifecycleScope |
runBlocking in production Android |
ANR on main thread | Use viewModelScope.launch |
| Ignoring structured concurrency | Memory leaks | Let scope manage coroutine lifetime |
Data classes with mutable properties (var) |
Accidental mutation | Prefer val in data classes |
Comparison table
| Feature | Kotlin | Java | Swift |
|---|---|---|---|
| Null safety | Type system (?) |
Annotations only | Optional type |
| Coroutines | Built-in | CompletableFuture | async/await |
| Data classes | data class |
Records (Java 16+) | struct |
| Extension functions | Yes | No | Yes |
| Type inference | Strong | Limited | Strong |
| Sealed classes | Yes | Sealed classes (17+) | Enum with associated values |
| Functional style | First-class | Streams API | Combine framework |
| Android | Primary language | Legacy | No |
| Multi-platform | Kotlin Multiplatform | No | No |
Common mistakes
| Mistake | Consequence | Fix |
|---|---|---|
val list = mutableListOf(...) leaked as mutable |
Unexpected mutations | Expose as List<T> with toList() |
delay() outside coroutine |
Compile error | Mark function suspend |
Using == on arrays |
Compares references | Use contentEquals() |
object for stateful singletons |
Hard to test | Use DI instead |
Not using coEvery in MockK tests |
suspend not mocked |
Always use coEvery/coVerify |
copy() on deeply nested data class |
Shallow copy | Use manual deep copy or @Serializable |
Forgetting viewLifecycleOwner in Fragment |
Lifecycle leak | Use viewLifecycleOwner.lifecycleScope |
Catching Exception in coroutines catching CancellationException |
Breaks cancellation | Catch specific exceptions or rethrow CancellationException |
FAQ
Is Kotlin replacing Java on Android? Yes — Google declared Kotlin the preferred language for Android in 2019. New Android APIs (Jetpack Compose, KSP) are Kotlin-first. Most new Android projects use Kotlin exclusively.
Can Kotlin run on iOS? Via Kotlin Multiplatform (KMP) you can share business logic (repositories, ViewModels, networking) between Android, iOS, web, and desktop. The iOS UI is still SwiftUI/UIKit. KMP is production-ready and used by Netflix, McDonald's, and VMware.
Is Kotlin slower than Java? At runtime — no. Kotlin compiles to the same JVM bytecode as Java. Compile times can be slightly longer due to features like coroutines and inline functions. Kotlin Native (non-JVM) may have different performance characteristics.
Do I need to learn Java before Kotlin? Not strictly. Kotlin is a good first JVM language. However, many Android libraries and legacy codebases are in Java, so Java reading ability is useful.
What is the difference between Kotlin and Kotlin Multiplatform?
Standard Kotlin targets the JVM (and Android). KMP adds compilation targets: iOS (via Kotlin/Native), browser (via Kotlin/JS), desktop (native or JVM). The shared commonMain code is regular Kotlin.
What are suspend functions not allowed to do?
You cannot call a suspend function from non-coroutine code without runBlocking (which blocks the calling thread). Suspend functions cannot be @JvmStatic. They also cannot be called from Java code directly without a coroutine wrapper.