Ответ
Корутины легковесны и работают поверх одного потока, используя механизм suspension/resume. Они не создают нативные потоки ОС, а выполняются в рамках пула потоков (Dispatchers). Каждая корутина занимает ~1KB памяти.
Потоки же — нативные объекты ОС, каждый требует ~1MB стека и создание/переключение между ними дорого. ОС ограничивает их количество (обычно ~1k-10k).
// 1000 корутин — легко
repeat(1000) {
launch(Dispatchers.IO) { /* work */ }
}
// 1000 потоков — упадёт с OOM
repeat(1000) {
Thread { /* work */ }.start()
}