Ты читал документацию. Смотрел видео. Писал viewModelScope.launch десятки раз. Можешь объяснить, что такое suspend-функция. Но на собеседовании, после третьего уточняющего вопроса про корутины, уверенность испаряется.

Проблема не в том, что ты не знаешь Coroutines. Проблема в том, что ты знаешь их на уровне API, а собеседование проверяет на уровне понимания. Разница — пропасть. И именно в неё падают 8 из 10 кандидатов с 1–2 годами опыта.

Ниже — разбор типичных вопросов, на которых сыплются. Не теория из документации, а то, что реально спрашивают и где именно ломаются ответы.


Ошибка №1: «Dispatchers.IO — для тяжёлых вычислений, Dispatchers.Default — для сети»

Что обычно отвечают

«Dispatchers.IO нужен для ввода-вывода: сеть, файлы, база данных. Dispatchers.Default для CPU-тяжёлых задач: парсинг JSON, сортировка.»

Почему это провал

Это заученная фраза из первого попавшегося туториала. Она формально верна, но интервьюер сразу копнёт глубже:

— А сколько потоков в Dispatchers.IO и в Dispatchers.Default? — Что произойдёт, если я запущу 200 сетевых запросов на Dispatchers.Default? — Можно ли переключиться внутри корутины с одного диспетчера на другой? Как?

Что ожидают услышать

Кандидат понимает модель пулов потоков, а не просто запоминает ярлыки:

  • Dispatchers.Default ограничен количеством ядер CPU (Runtime.getRuntime().availableProcessors()). Он для задач, которые грузят процессор.
  • Dispatchers.IO имеет пул до 64 потоков (или больше, в зависимости от версии) и оптимизирован под блокирующие операции. Потоки могут переиспользоваться между IO и Default.
  • Сетевые запросы через OkHttp/Retrofit уже асинхронны внутри. Запускать их на Dispatchers.IO часто избыточно — достаточно Dispatchers.Default или вообще Unconfined, если библиотека сама переключает контекст.
  • Переключение — через withContext(Dispatchers.IO) { ... }. Это не создаёт новую корутину, а перевешивает текущую на другой диспетчер.

Как звучит сильный ответ

«Dispatchers — это не про тип задачи, а про характер нагрузки. IO — для операций, где поток большую часть времени ждёт. Default — для операций, где поток реально считает. Если я запускаю Retrofit-запрос, мне не нужен IO, потому что OkHttp сам управляет своими потоками. А вот если я читаю большой файл побайтово — тогда да, withContext(Dispatchers.IO)


Ошибка №2: «structured concurrency — это когда корутины вложены друг в друга»

Что обычно отвечают

«Structured concurrency означает, что дочерние корутины привязаны к родителю и отменяются вместе с ним.»

Почему это ошибка

Это определение из вики, а не понимание. Интервьюер спросит:

  • Как распространяется отмена и исключения между родителем и детьми?
  • Что будет, если дочерняя корутина выбросит исключение? Умрёт ли родитель?
  • А если я не хочу, чтобы умирал?

Кандидат с заученным определением здесь зависает.

Что ожидают услышать

Понимание иерархии отмены и распространения исключений:

  • Корутина, запущенная в CoroutineScope, привязана к его Job. Отмена scope → отмена всех детей. Это и есть структура.
  • GlobalScope.launchнарушение structured concurrency. Корутина живёт сама по себе, не привязана к lifecycle. В Android это почти всегда баг.
  • Если дочерняя корутина падает с исключением, по умолчанию родитель тоже отменяется, а вместе с ним — все остальные дети. Это fail-fast behaviour.
  • Чтобы изолировать падение — SupervisorJob или supervisorScope. Тогда падение одной ветки не влияет на остальные.

Как звучит сильный ответ

«Structured concurrency — это гарантия, что у каждой корутины есть родитель и она не переживёт свой scope. В Android это критично: viewModelScope привязан к ViewModel, lifecycleScope — к Lifecycle. Если ViewModel очищается, все корутины отменяются, и я не получаю утечку. Если нужно, чтобы падение одной задачи не убивало остальные, я использую SupervisorJob — например, при загрузке независимых секций экрана параллельно.»


Ошибка №3: «Утечка памяти через корутины? Ну, я же в viewModelScope запускаю»

Что обычно отвечают

«Если использовать viewModelScope или lifecycleScope, утечек не будет, потому что scope сам отменяется.»

Почему это ошибка

Частично верно, но интервьюер проверит:

— Что если внутри корутины ты держишь ссылку на Activity? — А если используешь GlobalScope или создаёшь свой CoroutineScope и забываешь отменить? — Что будет, если suspend-функция бесконечно ждёт в delay() или в блокирующем вызове?

Что ожидают услышать

Понимание реальных сценариев утечек:

  • viewModelScope защищает от отмены корутины, но не от утечки объектов внутри неё. Если внутри launch ты захватил ссылку на Activity или Context, и корутина живёт дольше, чем Activity — утечка.
  • GlobalScope.launch — корутина живёт, пока не завершится сама. Если внутри бесконечный цикл или delay(Long.MAX_VALUE) — это утечка.
  • Собственный CoroutineScope(Dispatchers.Main) без вызова cancel() — та же проблема. Нужно отменять в onDestroy / onCleared.
  • Блокирующие вызовы (Thread.sleep, синхронный IO) на Main не отменяются через Job.cancel(). Корутина не может прервать блокирующий вызов — только дождаться его завершения.

Как звучит сильный ответ

«viewModelScope закрывает 90% случаев, но не все. Если я внутри корутины держу ссылку на View или Context, и корутина переживает их lifecycle — утечка. Поэтому я передаю applicationContext, если нужен Context, или использую WeakReference. И я помню, что cancel() корутины не прерывает блокирующий код — только suspend-точки. Если нужно прервать тяжёлую операцию, я проверяю isActive или использую withTimeout.»


Ошибка №4: «SupervisorJob — это чтобы корутины не отменялись»

Что обычно отвечают

«SupervisorJob нужен, чтобы при падении одной корутины не отменялись остальные.»

Почему это ошибка

Ответ в начале верен, но плоско. Интервьюер уточнит:

  • В чём разница между supervisorScope и CoroutineScope(SupervisorJob())?
  • Если использую SupervisorJob, исключения вообще куда-то деваются?
  • Что будет, если не поставить CoroutineExceptionHandler?

Что ожидают услышать

  • SupervisorJob меняет направление распространения исключений: ребёнок больше не отменяет родителю и брата. Но исключение не исчезает — оно поднимается до CoroutineExceptionHandler или крашит приложение.
  • supervisorScope { } — структурный аналог. Создаёт scope с SupervisorJob внутри текущей кору. Если упадёт сам supervisorScope, а не ребёнок — родитель всё равно отменится.
  • CoroutineScope(SupervisorJob() + Dispatchers.Main) — создаёт внешний scope. Его нужно отменять вручную.
  • Без CoroutineExceptionHandler в scope с SupervisorJob необработанное исключение вложиит к крашу. Это частый сценарий провала.

Как звучит сильный ответ

«SupervisorJob изолирует детей, но не проглатывает исключения. Мне всё равно нужен CoroutineExceptionHandler, иначе приложение упадёт. Я использую supervisorScope, когда загружаю несколько независимых блоков UI: если один упадёт, остальные доедут. Логирую ошибку через handler и показываю заменитель вместо краша.»


Ошибка №5: «withContext и async — это одно и то же, просто синтаксис другой»

Что обычно отвечают

«async возвращает Deferred, а withContext — результат напрямую. Но по сути оба переключают контекст.»

Почему это ошибка

Интервьюер спросит:

  • Что будет, если вызвать async и не вызвать await()?
  • withContext внутри async — это норма?
  • Если нужно запустить 10 запросов параллельно и дождаться всех?

Что ожидают услышать

  • async запускает новую корутину и возвращает Deferred. Результат не появится, пока не вызовешь await(). Если не вызвать — корутина отработает впустую, а исключение потеряется.
  • withContext не создаёт новую корутину. Это suspend-функция, которая переключает диспетчер. Она приостанавливает текущую корутину до завершения.
  • Для параллельных запросов — несколько async + awaitAll(). Или coroutineScope { } с несколькими async — тогда structured concurrency гарантирует отмену всех при падении одного.
  • withContext внутри async — нормально, если нужно сменить диспетчер для части работы.

Как звучит сильный ответ

«async — это запуск параллельной задачи. withContext — переключение внутри текущей задачи. Если нужно параллельно загрузить профиль и уведомления — делаю два async и awaitAll. Если переключиться на IO для чтения кэша — withContext. И я помню: async без await — молча проглоченное исключение.»


Ошибка №6: «Отмена корутины — это просто вызвать job.cancel()»

Что обычно отвечают

«Вызываю job.cancel(), и корутина останавливается.»

Почему это ошибка

  • Если внутри корутины цикл for (i in 1..1_000_000) без suspend-точек?
  • Что такое cooperative cancellation?
  • cancel() vs cancelAndJoin()?

Что ожидают услышать

  • Отмена в Coroutines — кооперативная. Корутина не убивается принудительно. Она проверяет флаг отмены только в suspend-точках: delay(), yield(), withContext(), await().
  • Если плотный CPU-цикл без suspend-вызовов, cancel() не прервёт его. Нужно вручную проверять isActive или ensureActive().
  • job.cancel() — запускает отмену и сразу возвращает управление. job.cancelAndJoin() — ждёт, пока корутина реально завершится.
  • withTimeout и withTimeoutOrNull — тоже механизм отмены. По истечении таймаута корутина отменяется через CancellationException.

Как звучит сильный ответ

«Отмена — это не kill, а просьба. Корутина увидит её только в следующей suspend-точке. Если тяжёлый цикл — ставлю yield() или проверяю isActive на каждой итерации. Если нужно дождаться реальной остановки перед следующим шагом — cancelAndJoin. И помню, что CancellationException — это не ошибка, а нормальное завершения.»


Что объединяет все ошибки

Одна и та же причина: знание API без понимания модели.

Курсы учат синтаксису: «напиши launch», «вызови withContext», «поставь Dispatchers.IO». Но не учат думать в терминах coroutine, не учат том, как распространяются исключения, кто принадлежит к lifecycle. Проверяют не «знаешь ли, что такое SupervisorJob», а «понимаешь ли, зачем он и что сломается, если не поставить».


Что делать прямо сейчас

  1. Перестань зубрить определения. Вместо «structured concurrency — это...» объясни вслух, что произойдёт в конкретном сценарии.
  2. Пиши мини-примеры и ломай их. Запусти корутину, отмени её, брось исключение, посмотри, что выживет.
  3. Проговаривай ответы вслух. Формат «я бы сделал X, потому что Y, а если не так — Z».
  4. Разбирай реальные вопросы с собеседований. Не абстрактные «расска\", а конкретные: «что вернёт этот код», «где утечка», «как это отменить».