Skip to content

Вопросы на собеседовании: Kotlin-разработчик

100 реальных вопросов с образцовыми ответами и пояснениями для уровня Junior Kotlin-разработчик.

Смотреть пример резюме: Kotlin-разработчик

Тренировка флешкарточками

Интервальное повторение · Hunter Pass

Вопросы

kotlinjvm

Компилятор Kotlin преобразует исходный код в байткод JVM, который может выполнить виртуальная машина.

  • kotlinc компилирует исходные файлы в файлы .class, которые часто упаковываются в JAR.
  • JVM загружает и выполняет этот байткод, поэтому приложению нужна совместимая среда Java.
  • Объявления Kotlin отображаются на типы JVM и сигнатуры байткода, совместимые с ABI Java, поэтому классы Kotlin и Java могут вызывать друг друга.

Зачем это спрашивают: Интервьюер проверяет, различаете ли вы компиляцию и выполнение и понимаете ли причину совместимости Kotlin с Java.

mutability

val создает ссылку только для чтения, а var создает ссылку, которой можно присвоить другое значение.

  • Предпочтительно использовать val, когда после инициализации ссылка должна указывать на то же значение.
  • val не делает сам объект неизменяемым, поэтому содержимое val MutableList по-прежнему можно менять.
  • var нужен, когда переприсваивание отражает реальное изменение состояния, например счетчика в цикле.

Зачем это спрашивают: Сильный ответ отличает переприсваивание ссылки от изменения объекта, на который она указывает.

Я использую вывод типа, когда инициализатор делает тип очевидным, и явный тип, когда он уточняет контракт.

  • val count = 3 понятен, потому что компилятор выводит Int и смысл не теряется.
  • Для публичных свойств и возвращаемых типов функций полезны явные типы, поскольку от этого API зависят вызывающие стороны.
  • Явный nullable-тип или интерфейс, например String? или List<String>, не дает детали реализации случайно стать контрактом.

Зачем это спрашивают: Интервьюер оценивает, используете ли вы вывод для читаемости, не превращая важные типы API в случайность.

Функция с блочным телом использует фигурные скобки и явные return, а функция с телом-выражением возвращает значение после знака равенства.

  • fun square(x: Int) = x * x лаконична, когда функция состоит из одного выражения.
  • Блочное тело подходит для нескольких инструкций, ранних возвратов или локальных переменных.
  • Компилятор может вывести возвращаемый тип тела-выражения, но для публичной функции явный тип понятнее.

Зачем это спрашивают: Интервьюер проверяет знание синтаксиса функций и выбор читаемой формы под сложность функции.

kotlin

Значения по умолчанию делают отдельные аргументы необязательными, а именованные аргументы показывают, какие параметры переданы при вызове.

  • Объявление fun connect(timeout: Int = 1000) задает стандартный тайм-аут.
  • connect(timeout = 3000) понятнее, чем необъясненное позиционное число.
  • Именованные аргументы позволяют пропустить предыдущие параметры со значениями по умолчанию без множества перегрузок.

Зачем это спрашивают: Интервьюер хочет увидеть знание синтаксиса вызова и его пользы для читаемости.

kotlin

if является выражением, потому что может вычислить значение для присваивания или возврата.

  • val label = if (score >= 60) "pass" else "fail" заменяет отдельную изменяемую переменную.
  • Когда требуется значение, обе ветки должны возвращать совместимые типы.
  • Такой стиль делает простое ветвление компактным и позволяет использовать val без последующего переприсваивания.

Зачем это спрашивают: Интервьюер проверяет понимание стиля Kotlin, в котором if может возвращать значение, а не только управлять ходом выполнения.

Я использую when, когда для одного значения или небольшого набора условий есть несколько отдельных веток.

  • Он может сопоставлять константы, диапазоны, типы или произвольные логические условия.
  • В роли выражения when напрямую возвращает значение выбранной ветки.
  • when по enum или sealed-классу может быть исчерпывающим, и компилятор укажет на пропущенный вариант.

Зачем это спрашивают: Сильный ответ объясняет читаемость и поддержку компилятора для конечного набора вариантов.

kotlin

Диапазоны Kotlin описывают последовательности упорядоченных значений, по которым может пройти цикл for.

  • 1..5 включает обе границы, а 1 until 5 не включает 5.
  • downTo перебирает значения вниз, а step меняет шаг.
  • Используйте for (item in items) для значений и withIndex, когда нужны индекс и значение.

Зачем это спрашивают: Интервьюер проверяет практическое знание синтаксиса итерации и границ диапазонов.

kotlin

Первичный конструктор объявляется в заголовке класса, а его параметры с val или var становятся свойствами.

  • class User(val name: String, var active: Boolean) хранит оба аргумента конструктора в каждом экземпляре.
  • Параметр без val или var доступен при инициализации, но не становится открытым свойством.
  • К свойству обращаются через user.name, а Kotlin создает нужные JVM-аксессоры.

Зачем это спрашивают: Интервьюер проверяет, умеете ли вы определить простой класс и не путаете ли параметры конструктора с хранимыми свойствами.

Блоки init выполняют общую инициализацию нового экземпляра, а вторичные конструкторы предлагают дополнительные способы создания.

  • Блоки init выполняются в порядке исходного кода вместе с инициализаторами свойств.
  • Если у класса есть первичный конструктор, каждый вторичный конструктор должен делегировать ему напрямую или через другой вторичный конструктор.
  • Значения по умолчанию или фабричные функции предпочтительнее, если они выражают варианты понятнее множества конструкторов.

Зачем это спрашивают: Интервьюер проверяет порядок инициализации и правило делегирования вторичного конструктора при наличии первичного.

typeskotlin

Интерфейс определяет возможность, которую может реализовать класс, а абстрактный класс также может хранить общее состояние экземпляра и логику конструктора.

  • Класс может реализовать несколько интерфейсов, но наследоваться только от одного класса.
  • Интерфейсы могут содержать реализации методов, но у них нет backing fields для хранимого состояния экземпляра.
  • Абстрактный класс подходит, когда связанные подклассы действительно разделяют состояние или protected-реализацию.

Зачем это спрашивают: Сильный ответ выбирает между несколькими возможностями и общим состоянием с инициализацией.

csskotlin

Kotlin использует public, internal, protected и private, чтобы управлять доступностью объявлений.

  • public используется по умолчанию и открывает объявление везде, где виден его владелец.
  • internal ограничивает доступ одним модулем компиляции, например одним модулем Gradle.
  • protected открывает члены класса подклассам, а private ограничивает доступ содержащим классом или файлом.

Зачем это спрашивают: Интервьюер проверяет знание модульной видимости internal и основных границ доступа.

Объявление object создает именованный singleton, экземпляр которого инициализируется при первом обращении.

  • Оно подходит для утилит без состояния или одной общей реализации, например object JsonDefaults.
  • К членам обращаются по имени объекта без вызова конструктора.
  • Общее изменяемое состояние внутри object остается глобальным и требует аккуратной синхронизации и тестирования.

Зачем это спрашивают: Интервьюер хочет услышать про singleton и понимание того, что object не делает изменяемое состояние безопасным.

classes

Companion object хранит члены, связанные с классом, а не с отдельным экземпляром.

  • User.create() можно вызвать, если create объявлена в companion object класса User.
  • Там часто размещают фабричные функции и константы, которые по смыслу принадлежат классу.
  • Это настоящий объект, который может реализовать интерфейс, а не просто аналог Java-синтаксиса static.

Зачем это спрашивают: Интервьюер проверяет понимание членов, связанных с классом, и объектной реализации этого механизма в Kotlin.

Data-класс генерирует методы для работы со значениями на основе свойств первичного конструктора.

  • Он предоставляет equals и hashCode на основе этих свойств конструктора.
  • Также генерируются читаемый toString, component-функции и copy.
  • Data-класс подходит для записей вроде UserDto, но при необходимости может содержать поведение и инварианты.

Зачем это спрашивают: Сильный ответ называет сгенерированное поведение и объясняет применение для записей, не сводя data-класс к пустому контейнеру.

componentsclasses

copy создает новый экземпляр data-класса с измененными выбранными свойствами конструктора, а component-функции поддерживают деструктуризацию.

  • user.copy(name = "Mina") сохраняет остальные значения первичного конструктора.
  • val (name, active) = user вызывает component1 и component2 в порядке свойств конструктора.
  • copy выполняет поверхностное копирование, поэтому вложенные изменяемые объекты остаются общими, если не скопировать их отдельно.

Зачем это спрашивают: Интервьюер проверяет практическое использование data-класса и понимание того, что copy не клонирует граф объектов рекурсивно.

sealed-classes

Я использую enum для фиксированного набора singleton-констант, а sealed-класс для закрытого набора вариантов с разными данными.

  • Direction.NORTH является одной enum-константой, и у всех констант одинаковая объявленная форма.
  • Sealed-иерархия может представить Loading, Success(data) и Error(message) разными типами.
  • Оба варианта позволяют сделать when исчерпывающим, если обработаны все известные случаи.

Зачем это спрашивают: Интервьюер оценивает, умеете ли вы отделять простые фиксированные значения от вариантов состояния с данными.

kotlin

== проверяет структурное равенство, а === проверяет, указывают ли две ссылки на один объект.

  • a == b вызывает безопасный вариант equals, включая корректную обработку null в a.
  • a === b дает true только для одной и той же ссылки на объект.
  • Используйте == для значений вроде строк и data-классов, а === только когда важна сама идентичность.

Зачем это спрашивают: Сильный ответ отличает равенство значений от идентичности объектов и правильно выбирает оператор.

kotlin

Интерфейсы read-only коллекций не предоставляют операций изменения, а изменяемые интерфейсы добавляют add, remove и похожие операции.

  • listOf возвращает List, а mutableListOf возвращает MutableList.
  • Параметр List сообщает, что функция не будет менять коллекцию через эту ссылку.
  • Read-only представление не гарантирует глубокую неизменяемость, потому что другая mutable-ссылка может менять ту же коллекцию.

Зачем это спрашивают: Интервьюер проверяет, не приравниваете ли вы read-only интерфейсы Kotlin к гарантированной неизменяемости.

Я выбираю коллекцию по способу идентификации значений и требованиям к порядку и дубликатам.

  • List хранит упорядоченную последовательность и разрешает повторяющиеся элементы.
  • Set представляет уникальные элементы и удобен для проверки вхождения.
  • Map хранит значения по уникальным ключам, например User по идентификатору пользователя.

Зачем это спрашивают: Интервьюер хочет услышать практическое различие трех основных видов коллекций.

Закрытые вопросы

  • 21

    Что делают map и filter с коллекцией?

  • 22

    Для чего используется fold?

  • 23

    Как Kotlin различает nullable и non-null типы?

    fundamentalskotlinnull-safety
  • 24

    Как вместе работают safe call и оператор Elvis?

    null-safety
  • 25

    Как let может помочь с nullable-значением?

    null-safety
  • 26

    Почему оператор утверждения non-null рискован?

    fundamentals
  • 27

    Что такое проверка типов и smart cast в Kotlin?

    kotlintype-casts
  • 28

    Что делает оператор безопасного приведения типа?

    type-casts
  • 29

    Что такое лямбда в Kotlin?

    lambdakotlin
  • 30

    Что такое функция высшего порядка?

    functions
  • 31

    Что такое extension-функция?

    extensions
  • 32

    Какую проблему решают обобщенные типы?

    generics
  • 33

    Как на начальном уровне выбирать scope-функцию Kotlin?

    kotlinscope-functions
  • 34

    Как try работает в роли выражения в Kotlin?

    kotlin
  • 35

    Что такое platform types из Java в Kotlin?

    kotlinjava-interop
  • 36

    Что означает suspend у Kotlin-функции?

    kotlincoroutines
  • 37

    Когда использовать launch, а когда async?

    asynccoroutines
  • 38

    Что такое Job и delay в коде с корутинами?

    coroutines
  • 39

    Что означает структурированная конкурентность?

    concurrencystructured-concurrency
  • 40

    Что означает холодный Flow?

    flow
  • 41

    Как работают состояние и рекомпозиция в Jetpack Compose?

    composecompose-recomposition
  • 42

    Для чего нужен Android ViewModel?

    viewmodel
  • 43

    Какую проблему решает Room в Android?

    room
  • 44

    За что отвечает Gradle в Kotlin-проекте?

    buildkotlin
  • 45

    Как написать простой unit-тест для Kotlin-функции?

    unitunit-testingkotlin
  • 46

    Для чего Ktor и Spring используются в Kotlin backend-разработке?

    ktorkotlin
  • 47

    Как работают шаблоны строк в Kotlin?

    kotlin
  • 48

    Как свойство Kotlin может настраивать чтение и запись?

    kotlin
  • 49

    Как работает наследование, если классы Kotlin по умолчанию final?

    oopkotlinownership
  • 50

    Что означает Unit как возвращаемый тип Kotlin?

    kotlin
  • 51

    Функция получает name: String?, а name.length не компилируется. Как вернуть длину, считая отсутствующее имя пустым?

  • 52

    Экран получает permissionGranted: Boolean?, где null означает, что разрешение ещё не проверено. Как разрешить действие только при явно выданном разрешении?

    fundamentals
  • 53

    Java-метод с сигнатурой String getToken() иногда возвращает null, и Kotlin падает на token.length, хотя предупреждения о nullable не было. Как это исправить?

    fundamentalskotlinnull-safety
  • 54

    Приложение падает на user.email!!.lowercase(), когда у старого аккаунта нет email. Какое локальное исправление вы внесёте?

  • 55

    Fragment падает с UninitializedPropertyAccessException, когда binding.title читается до onCreateView. Как исправить использование lateinit?

    android-fragments
  • 56

    Код объявляет val total = 0, а затем выполняет total += price в цикле и получает Val cannot be reassigned. Каково минимальное корректное изменение?

  • 57

    Разработчик удивлён, что val items = mutableListOf<String>() всё ещё разрешает items.add("A"). Это ошибка, и как сделать содержимое доступным только для чтения?

  • 58

    Функция получает List<String> и не компилируется на names.add("Sam"). Как выполнить задачу, не ослабляя контракт функции?

  • 59

    Два разных экземпляра User с одинаковыми данными сравниваются как неравные, потому что код использует first === second. Что нужно изменить?

    distinct
  • 60

    Тест ожидает, что intArrayOf(1, 2) == intArrayOf(1, 2) даст true, но он падает. Как сравнить массивы?

  • 61

    В sealed UiState добавили подтип Empty, и существующее выражение when перестало компилироваться. Что нужно сделать?

    sealed-classes
  • 62

    После val updated = order.copy() код вызывает updated.lines.add(item), и исходный order.lines тоже меняется. Почему и какое локальное исправление подойдёт?

  • 63

    Цепочка использует apply для вычисления String, но её результатом оказывается исходный Builder; другая цепочка применяет let только для настройки Builder. Как выбрать между let, apply и run?

    config
  • 64

    Extension fun Animal.label() = "animal" всё равно возвращает "animal" для Dog, сохранённого в переменной Animal; одноимённый member также игнорирует extension. Почему?

  • 65

    Парсер API создаёт List<String?>, но renderNames ожидает List<String>. Как безопасно устранить несовпадение типов?

    api
  • 66

    Функция пытается передать MutableList<Int> параметру типа MutableList<Number>, и Kotlin это запрещает. Каково практическое исправление?

    kotlin
  • 67

    Query-параметр Ktor page разбирается через page.toInt(), и некорректный ввод возвращает ошибку 500. Как его парсить?

    queriesktor
  • 68

    Запрос содержит sort=recent, но enumValueOf<Sort>(sort) падает, потому что константы enum записаны в верхнем регистре. Как сделать парсинг надёжным?

  • 69

    Код оборачивает парсинг файла в catch (e: Exception) и сообщает Invalid file для любой ошибки, включая отмену и сбой диска. Как сузить обработку?

    resilienceerror-handling
  • 70

    Парсер открывает inputStream и не закрывает его, если decode выбрасывает исключение. Какую конструкцию Kotlin вы используете?

    kotlin
  • 71

    ViewModel запускает async { repository.load() } только ради побочного эффекта и никогда не использует Deferred. Следует применить launch или async?

    asynccoroutinesviewmodel
  • 72

    Две цены загружаются через val retail = async { api.retailPrice() } и val member = async { api.memberPrice() }, но UI пытается вычесть значения Deferred. Чего не хватает?

    asynccoroutinesapi
  • 73

    Исключение выходит из viewModelScope.launch, пока состояние остаётся Loading. Что может произойти в Android и как обработать этот локальный сбой?

    error-handlingcoroutinesviewmodel
  • 74

    Обработчик кнопки вызывает Thread.sleep(1000) перед обновлением Compose UI, и приложение зависает на секунду. Чем это заменить?

    concurrency
  • 75

    Долгая coroutine перехватывает Exception, пишет лог и продолжает работу даже после ухода пользователя с экрана. Какую ошибку отмены нужно исправить?

    error-handlingcoroutinesresilience
  • 76

    Fragment создаёт CoroutineScope(Dispatchers.Main) и продолжает collect после уничтожения его View. Какой scope вы используете?

    coroutinescoroutine-dispatchersandroid-fragments
  • 77

    Coroutine выполняет большой CPU-цикл без suspending-вызовов и игнорирует cancel() до конца цикла. Как сделать отмену кооперативной?

    coroutines
  • 78

    Функция repository возвращает Flow<List<Item>>, но вызов repository.items() не запускает запрос к базе и не обновляет UI. Чего не хватает?

    databasequeriesflow
  • 79

    Экран вызывает collect одного холодного сетевого Flow в двух разных launch-блоках, и запрос выполняется дважды. Как убрать повторную работу?

    flowcoroutines
  • 80

    Flow отдаёт все товары, но экрану нужны только названия товаров в наличии. Какие простые операторы вы примените?

    flow
  • 81

    Сбор Flow завершается из-за IOException, и экран не получает состояние Error. Где обработать сбой?

    flow
  • 82

    Compose-счётчик объявляет var count = 0 внутри composable, поэтому каждая recomposition сбрасывает его. Каково локальное исправление?

    compose-recomposition
  • 83

    Composable хранит remember { mutableListOf<Item>() }; items.add(newItem) выполняется, но список на экране не обновляется. Как изменить способ хранения состояния?

  • 84

    Строки LazyColumn сохраняют неправильное состояние checkbox после вставки элемента в начало. Что нужно добавить?

    compose-lists
  • 85

    Composable напрямую запускает запрос repository и повторно загружает данные после поворота. Как перенести это состояние во ViewModel?

    viewmodelcoroutines
  • 86

    Вызов DAO Room из обработчика нажатия выбрасывает Cannot access database on the main thread. Каково правильное исправление?

    databasequeriesroom
  • 87

    DAO Room возвращает List<Task>, поэтому экран не обновляется после другой вставки. Какой возвращаемый тип сделает запрос наблюдаемым?

    queriesroomdata-access
  • 88

    После повышения версии базы Room с 1 до 2 и добавления NOT NULL столбца приложение существующих пользователей падает из-за отсутствующей migration. Что добавить?

    databasemigrationsschema
  • 89

    Upload в WorkManager возвращает Result.failure() при отсутствии сети, поэтому больше не запускается. Что нужно вернуть при временном сетевом сбое?

    background-work
  • 90

    Hilt сообщает, что не может предоставить PaymentRepository: для интерфейса не указана реализация. Какого локального DI-объявления не хватает?

    dependency-injectiontypes
  • 91

    Destination деталей ожидает Int itemId, но navigation передаёт строку abc и падает при преобразовании аргумента. Как это предотвратить?

  • 92

    Route Ktor для POST вызывает call.receive<CreateUser>(), и неверный JSON превращается в непонятную серверную ошибку. Как обработать запрос?

    ktor
  • 93

    Ktor client вызывает response.body<User>() для каждого ответа и падает, когда сервер возвращает 404 с телом ошибки. Что нужно сделать сначала?

    ktor
  • 94

    Gradle сообщает о дубликатах классов Kotlin runtime: kotlin-stdlib 2.x разрешён вместе со старой транзитивной зависимостью kotlin-stdlib-jdk8 1.7, классы которой вошли в kotlin-stdlib начиная с 1.8. Как найти и исправить причину?

    dependenciesbuildkotlin
  • 95

    После разрешения Git-конфликта в build.gradle.kts сборка сообщает об отсутствующей зависимости. Что проверить перед отправкой merge?

    gitdependenciesbuild
  • 96

    Unit test для suspend-функции использует runBlocking и настоящие delay, из-за чего набор тестов работает медленно. Какую настройку coroutine-теста вы выберете?

    unitunit-testingcoroutine-testing
  • 97

    Тест запускает collection конечного Flow, но проверяет результат до появления значений. Как сделать этот простой Flow-тест детерминированным?

    flowcoroutines
  • 98

    Тест ViewModel проверяет uiState сразу после запуска mock-загрузки и всё ещё видит Loading. Как построить тест?

    viewmodel
  • 99

    Репозиторий перехватывает исключение API и должен возвращать результат с ошибкой, но его модульный тест покрывает только успешный случай. Какой точечный тест вы добавите?

    unitunit-testingapi
  • 100

    Тест calculateDiscount передаёт только одну обычную цену, и ошибка проявляется на нулевом и отрицательном вводе. Как улучшить базовый набор unit tests?

    unitunit-testing