Challenge 3: Choosing a Dispatcher — Solution fun Int.isPrime(): Boolean { if (this < 2) return false for (i in 2 until this) { if (this % i == 0) return false } return true } suspend fun calculatePrimesUpTo(limit: Int): List = withContext(Dispatchers.Default) { (2..limit).filter { it.isPrime() } } // Why this needs an explicit dispatcher, while Room/Retrofit calls in // this chapter's other examples didn't: // // calculatePrimesUpTo does genuine CPU-bound work — checking primality // for every number up to "limit" keeps a CPU core continuously busy // computing, with no waiting involved at all. That's exactly the kind // of work Dispatchers.Default's CPU-core-sized thread pool exists for. // Room's suspend DAO functions and Retrofit's suspend API calls, by // contrast, are library functions that already internally dispatch // their actual I/O work to an appropriate thread pool (typically // Dispatchers.IO-equivalent) behind the scenes — calling them from // viewModelScope.launch { } with no explicit dispatcher is already // correct, because the library itself already did the dispatching. A // hand-written CPU-bound function like this one has no such built-in // behavior, so it must explicitly wrap itself in withContext(Dispatchers.Default) // to avoid running on (and blocking) whatever dispatcher its caller // happens to be using — which could well be the Main dispatcher if // called carelessly. Notes: - Int.isPrime() reuses the exact extension function from Kotlin Fundamentals Chapter 8, applied here to a genuinely CPU-heavy loop over a potentially large range. - withContext(Dispatchers.Default) returns its lambda's result directly, which is why calculatePrimesUpTo can be written as a single-expression function returning that withContext call's result. - Dispatchers.Default (not Dispatchers.IO) is the correct choice here specifically because this is compute-bound work, not I/O-bound waiting — the distinction the chapter draws between the two dispatcher types.