Irbisa · Cheatsheet

Kotlin Basics

val/var, types, null safety, control flow, functions, lambdas

Junior Developer · 24 questions

  1. 01

    What are the advantages of Kotlin over Java for Android, and what are its drawbacks?

    Kotlin removes whole classes of Java bugs and boilerplate (nullability in the type system, data classes, coroutines) while running on the same JVM and calling Java directly; the price is build time, some hidden runtime costs and a weak spot at the Java boundary.

  2. 02

    What is the difference between val, var and const val in Kotlin?

    val is a read-only reference assigned once, var can be reassigned, and const val is a compile-time constant whose value the compiler copies into every place that uses it.

    val name = "Ada"                  // type inferred as String// name = "Bob"                   // compile error: val cannot be reassigned var attempts = 0attempts += 1                     // fine…
  3. 03

    How does null safety work in Kotlin: nullable types, ?., ?:, !! and let?

    Nullability is part of the type: a String can never hold null, a String? can, and the compiler will not let you use a String? as a String until you have handled the null case.

    fun greet(user: User?) {    val city = user?.address?.city          // String?: null if any link is null    val name = user?.name ?: "Guest"        // fallback value    user?.let { analytics.track(it.id) }    // runs only when user != null     if (user == null) return…
  4. 04

    What does describe(null) print here, line by line?

    fun describe(name: String?) {    println(name?.length)    println(name?.length ?: -1)    println(name.isNullOrEmpty())    println("Hello, $name")    println(name!!.length)} fun main() {    describe(null)}

    It prints null, -1, true and Hello, null, and then the last line throws NullPointerException: only !! crashes, every other operator here handles null on purpose.

  5. 05

    lateinit vs by lazy: when do you use each, and how do you check that a lateinit property is initialized?

    lateinit var is for a non-null value that something else assigns later, such as Hilt or onCreate; by lazy is for a val that computes itself on first read and caches the result.

    @AndroidEntryPointclass ProfileActivity : AppCompatActivity() {    @Inject lateinit var repo: UserRepository        // assigned by Hilt    private lateinit var binding: ActivityProfileBinding     private val dateFormat by lazy {                 // created on first use…
  6. 06

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

    == checks structural equality by calling equals(), while === checks referential identity: whether both references point to the very same object.

    data class User(val id: Int, val name: String) val a = User(1, "Ada")val b = User(1, "Ada")println(a == b)      // true: equals() compares propertiesprintln(a === b)     // false: two different objects…
  7. 07

    How does when work as an expression in Kotlin, and when must it be exhaustive?

    when is Kotlin's switch, but it is an expression that returns the value of the matching branch; when you use that value, the branches must cover every input, through else or by listing every case of an enum, sealed type or Boolean.

    sealed interface UiState {    data object Loading : UiState    data class Content(val items: List<String>) : UiState    data class Error(val message: String) : UiState}…
  8. 08

    How do ranges work in Kotlin: .., ..<, until, downTo and step?

    A range start..end includes both ends; ..< and until exclude the end, downTo counts backwards, and step changes the stride.

    val items = listOf("a", "b", "c") // for (i in 0..items.size) println(items[i])   // wrong: crashes when i == 3for (i in 0..<items.size) println(items[i])     // end excludedfor (i in 0 until items.size) println(items[i]) // same, older spellingfor (i in items.indices) println(items[i])…
  9. 09

    What are lambdas and higher-order functions in Kotlin, and what does a lambda compile to?

    A lambda is a function literal such as { x: Int -> x * 2 } that you can store or pass around; a higher-order function takes a function as a parameter or returns one, like filter, map or your own retry(block: () -> Boolean).

    // Higher-order function: takes a function as a parameterfun retry(times: Int, block: () -> Boolean): Boolean {    repeat(times) { if (block()) return true }    return false}…
  10. 10

    What does this counter code print, and where does count live after makeCounter() returns?

    fun makeCounter(): () -> Int {    var count = 0    return { ++count }} fun main() {    val first = makeCounter()    val second = makeCounter()    println(first())    println(first())    println(second())}

    It prints 1, 2, 1: each call to makeCounter() creates its own count, and the returned lambda captures it, so first and second keep separate counters that outlive the function call.

  11. 11

    How do default and named arguments work in Kotlin, and how do they replace the Builder pattern?

    A parameter can declare a default value, so callers pass only what differs, and any argument can be passed by name; together they replace telescoping constructors and most hand-written Builders.

    data class Request(    val url: String,    val method: String = "GET",    val timeoutMs: Long = 10_000,    val headers: Map<String, String> = emptyMap(),)…
  12. 12

    What is the difference between List and MutableList, and between a val mutable list and a var read-only list?

    List is a read-only interface with no add or remove, MutableList adds them; val vs var is a separate choice: it decides whether the variable can point to another list, not whether the list can change.

    class Cart {    private val _items = mutableListOf<String>()   // mutable inside    val items: List<String> get() = _items         // read-only outside     fun add(item: String) { _items += item }}…
  13. 13

    What do map, flatMap, fold, reduce, groupBy, associateBy, partition and zip do on a Kotlin collection?

    They are standard-library extensions that build a new collection or value without changing the original: map converts each element, fold/reduce combine all elements into one value, and the rest regroup elements into lists, maps or pairs.

    data class Order(val id: Int, val user: String, val total: Long, val items: List<String>) val orders = listOf(    Order(1, "ada", 300, listOf("pen", "ink")),    Order(2, "bob", 0, emptyList()),    Order(3, "ada", 150, listOf("paper")),…
  14. 14

    When do you use an Array instead of a List, and why does arrayOf(1, 2) == arrayOf(1, 2) return false?

    Arrays are fixed-size JVM arrays that compare by reference, so == is false for two arrays with the same contents; use List by default and arrays only where an API, vararg or primitive performance needs them.

    val a = arrayOf(1, 2)val b = arrayOf(1, 2)println(a == b)                        // false: reference comparisonprintln(a.contentEquals(b))            // trueprintln(a.contentToString())           // [1, 2]println(listOf(1, 2) == listOf(1, 2))  // true…
  15. 15

    How do destructuring declarations work in Kotlin?

    val (a, b) = obj calls obj.component1() and obj.component2() in order; the names you write do not matter, only the position.

    data class Size(val width: Int, val height: Int) val (w, h) = Size(1080, 1920)        // component1(), component2() val prices = mapOf("apple" to 3, "pear" to 5)for ((fruit, price) in prices) println("$fruit costs $price")…
  16. 16

    Why is building a string with += in a loop slow, and what do you use instead?

    String is immutable, so each += allocates a new string and copies every character so far: n appends cost O(n²); StringBuilder, usually through buildString { } or joinToString(), appends into one growing buffer.

    // wrong: a new String on every iteration, O(n²) copyingvar csv = ""for (u in users) csv += u.name + "," // right: one bufferval csvFast = buildString {…
  17. 17

    What are smart casts in Kotlin, when does the compiler refuse them, and how do as and as? differ?

    After a check such as x is String or x != null, the compiler treats x as the narrower type in that branch without an explicit cast, but only when it can prove the value cannot change between the check and the use.

    fun describe(x: Any): String {    if (x is String) return "text of ${x.length}"  // smart cast to String    if (x !is Int) return "unknown"    return "number ${x + 1}"                       // smart cast to Int}…
  18. 18

    Find the bug: a signed-in user who opens the profile for the first time also sees the guest banner.

    data class User(val id: String, val name: String) class ProfileScreen {    private val lastSeenName = mutableMapOf<String, String>()     fun render(user: User?) {        user?.let { lastSeenName.put(it.id, it.name) } ?: showGuestBanner()        showGreeting(user?.name ?: "guest")    }     private fun showGuestBanner() = println("Sign in to sync your progress")    private fun showGreeting(name: String) = println("Hello, $name")}

    ?.let { } ?: showGuestBanner() checks the result of the lambda, not user: lastSeenName.put() returns the previous value, which is null on a first visit, so a signed-in user sees the guest banner too.

  19. 19

    Can a val property return a different value each time you read it?

    Yes, val only means there is no setter; a val with a custom getter has no backing field and runs the getter on every read, so it can return a new value each time.

    class Basket {    private val items = mutableListOf<Item>()     val size: Int get() = items.size        // no backing field, recomputed    val isEmpty: Boolean get() = size == 0…
  20. 20

    Find the bug: after one user logs out and another signs in, every request fails with 401 until the app is restarted.

    data class Request(val path: String, val headers: Map<String, String>) class Session {    var token: String? = null              // replaced on every login} class ApiClient(private val session: Session) {  // one instance for the app    private val authHeader by lazy { "Bearer ${session.token}" }     fun request(path: String): Request =        Request(path, headers = mapOf("Authorization" to authHeader))}

    by lazy computes authHeader once, on the first request, and caches it for the life of the single ApiClient, so after a re-login every request still sends the previous user's token and the server answers 401.

  21. 21

    Does Kotlin have primitive types, and when does an Int get boxed?

    Kotlin has no separate primitive types in the language (Int looks like any class), but the compiler stores a non-null Int, Long, Double or Boolean as a JVM primitive wherever it can and boxes it only when an object is required.

    val count: Int = 42                 // int on the JVMval maybe: Int? = 42                // java.lang.Integer: nullable means boxedval list: List<Int> = listOf(1, 2)  // List<Integer>: generics always boxval raw = IntArray(1_000)           // int[], no boxing // val total: Long = count          // compile error: no implicit widening…
  22. 22

    What are Any, Unit and Nothing in Kotlin, and how many instances of each can exist?

    Any is the root of all non-null types with any number of instances, Unit has exactly one instance and plays the role of Java's void, and Nothing has no instances at all: it is the type of an expression that never completes.

    fun log(message: String) {           // returns Unit implicitly    println(message)}val onDone: () -> Unit = { log("done") } fun fail(message: String): Nothing = throw IllegalArgumentException(message)…
  23. 23

    Does Kotlin have checked exceptions, and how do you signal and handle failures instead?

    No, nothing in a Kotlin signature forces the caller to catch, so expected failures go into the return type (a sealed result or Result<T>), and exceptions are kept for bugs and truly exceptional conditions.

    sealed interface LoginResult {    data class Success(val user: User) : LoginResult    data object WrongPassword : LoginResult    data object Offline : LoginResult}…
  24. 24

    What does this forEach code print, and how do you make it skip only the blank item?

    fun printVisible(items: List<String>) {    items.forEach {        if (it.isBlank()) return        println(it)    }    println("done")} fun main() {    printVisible(listOf("a", " ", "b"))}

    It prints only a: forEach is an inline function, so a bare return inside its lambda returns from printVisible itself, and neither b nor done is printed.