Как пройти собеседование по Kotlin: топ вопросов продвинутого уровня

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Как пройти собеседование по Kotlin: топ вопросов продвинутого уровня

Собеседование на позицию Senior Kotlin-разработчика — это не просто проверка синтаксиса. Интервьюеры хотят увидеть глубокое понимание архитектуры языка, особенностей компиляции и многопоточности. В этой статье мы разберём самые сложные и часто задаваемые вопросы, которые помогут вам уверенно пройти техническое интервью. Материал ориентирован на разработчиков, уже имеющих опыт работы с Kotlin и Java.

1. Корутины: Structured Concurrency и отмена задач

Корутины — одна из сильнейших сторон Kotlin. Продвинутый вопрос часто касается Structured Concurrency. Интервьюер может спросить: «Что произойдёт, если дочерняя корутина выбросит исключение, а родительская её не перехватит?»

Ответ кроется в иерархии Job. Если дочерняя корутина (запущенная через coroutineScope или внутри launch) падает с исключением, оно всплывает к родителю. Родительская корутина отменяется, и все её дочерние задачи тоже. Это делает код предсказуемым и безопасным.

Пример, который стоит показать на собеседовании:

import kotlinx.coroutines.*

fun main() = runBlocking {
    try {
        coroutineScope {
            launch {
                delay(100)
                throw RuntimeException("Ошибка в дочерней корутине")
            }
            launch {
                delay(200)
                println("Эта корутина не выполнится, если первая упадёт")
            }
        }
    } catch (e: Exception) {
        println("Исключение перехвачено: ${e.message}")
    }
    println("Программа продолжает работу")
}

Ещё один частый вопрос — отмена корутин. Как правильно отменить корутину? Используйте Job.cancel() и проверяйте состояние через ensureActive() или isActive внутри длительных операций.

2. Inline-функции и reified-типы

Продвинутые разработчики должны понимать, зачем нужны inline-функции. Вопрос может звучать так: «Почему Kotlin не позволяет использовать reified-типы без inline?»

Ответ: reified (овеществлённые) типы работают только внутри inline-функций, потому что код такой функции встраивается прямо в место вызова. Компилятор подставляет конкретный тип во время компиляции, что невозможно для обычных generic-функций из-за стирания типов в JVM.

Пример, который демонстрирует силу reified:

inline fun <reified T> isInstance(value: Any): Boolean {
    return value is T
}

fun main() {
    println(isInstance<String>("Hello")) // true
    println(isInstance<Int>("Hello"))    // false
}

Также спросят про noinline и crossinline. noinline запрещает встраивание конкретного лямбда-параметра, а crossinline запрещает использовать return внутри лямбды (нелокальный возврат).

3. Делегаты: Delegates.observable, vetoable и собственные делегаты

Вопрос про делегаты — классика. Продвинутый уровень подразумевает не только знание встроенных делегатов (lazy, observable), но и умение писать свои.

Пример: «Как реализовать делегат, который логирует каждое изменение свойства?»

import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

class LoggingDelegate<T>(private val initialValue: T) : ReadWriteProperty<Any?, T> {
    private var value = initialValue

    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        println("Чтение ${property.name}: $value")
        return value
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        println("Запись ${property.name}: $value")
        this.value = value
    }
}

class User {
    var name: String by LoggingDelegate("Unknown")
}

fun main() {
    val user = User()
    user.name = "Анна"
    println(user.name)
}

Также спросят про Delegates.observable и vetoable. vetoable позволяет отклонить изменение, вернув false в лямбде-обработчике.

4. Sealed-классы и when-выражения: полнота проверки

Sealed-классы — мощный инструмент для моделирования ограниченной иерархии. Вопрос: «В чём разница между sealed class и enum class? Когда что использовать?»

Ответ: enum — это фиксированный набор констант (каждый экземпляр — синглтон). sealed class может иметь разные подклассы с разными состояниями и данными. Kotlin гарантирует, что when-выражение на sealed-классе является исчерпывающим (exhaustive), если вы обработали все подклассы. Это избавляет от лишнего else-блока.

Пример:

sealed class NetworkResult<out T> {
    data class Success<T>(val data: T) : NetworkResult<T>()
    data class Error(val message: String, val code: Int) : NetworkResult<Nothing>()
    object Loading : NetworkResult<Nothing>()
}

fun handleResult(result: NetworkResult<String>) {
    when (result) {
        is NetworkResult.Success -> println("Успех: ${result.data}")
        is NetworkResult.Error -> println("Ошибка: ${result.message} (код ${result.code})")
        NetworkResult.Loading -> println("Загрузка...")
    }
    // Компилятор не требует else, так как все ветки покрыты
}

Продвинутый вопрос: «Можно ли добавить новый подкласс sealed-класса в другом файле?» Нет, все подклассы должны быть объявлены в том же пакете и файле (для sealed class) или в том же модуле (для sealed interface, начиная с Kotlin 1.5).

Заключение

Продвинутое собеседование по Kotlin проверяет не знание синтаксиса, а понимание принципов работы языка: как корутины управляют потоками, как inline-функции влияют на байт-код, как делегаты экономят память, и как sealed-классы делают код безопаснее. Регулярно практикуйтесь на Codewars, чита

Блоги

Рекомендаций по книгам