Почему корутины называются легковесными
Что проверяют
Понимаете ли вы, что корутина это не поток, а объект с состоянием. Ответ «они лёгкие, потому что их можно запустить много» не засчитывают: ждут механизм, то есть приостановку без удержания потока, и понимание того, где лёгкость пропадает.
Как отвечать
Компилятор превращает suspend-функцию в конечный автомат: каждая точка приостановки это состояние, а локальные переменные переезжают в объект Continuation в куче. Приостановка это обычный возврат из функции, после которого поток свободен и берёт следующую задачу. Возобновление это вызов продолжения с сохранённым состоянием.
Отсюда вся арифметика. Поток платформы требует стека и участия ядра при переключении, поэтому тысячи потоков это уже много. Приостановленная корутина это небольшой объект, и сотни тысяч живут спокойно: переключение стоит как вызов метода, ядро в нём не участвует.
viewModelScope.launch {
val user = withContext(Dispatchers.IO) { repo.load(id) } // поток не заблокирован
render(user)
}Добавьте оговорку, ради неё вопрос часто и задают: лёгкость исчезает, как только внутри корутины блокируют поток. Thread.sleep, синхронный вызов драйвера базы, тяжёлый расчёт занимают поток диспетчера целиком. Dispatchers.Default рассчитан на число ядер, а пул Dispatchers.IO шире именно потому, что там ждут блокирующего ввода и вывода.
Стоит упомянуть и структурированную конкурентность: viewModelScope отменяет детей вместе с экраном.
Где спотыкаются
Повторяют формулу «корутина это лёгкий поток» буквально и падают на вопросе про Thread.sleep внутри. Поток встанет, соседние корутины на том же диспетчере встанут вместе с ним, а delay именно приостанавливает и планирует возобновление.
Второе: считают, что suspend сам по себе уводит работу в фон. Модификатор лишь разрешает приостановку, а место выполнения задаёт контекст. Тяжёлый цикл в suspend-функции на главном диспетчере подвесит интерфейс ровно так же, как обычный вызов.
Третье: запускают через GlobalScope и теряют отмену: корутина переживает экран и трогает мёртвые объекты. Рядом второй промах, перехват CancellationException общим блоком, после которого отмена перестаёт работать.