You need to limit concurrent calls to a downstream service to 10. Why prefer Dispatchers.IO.limitedParallelism(10) over creating Executors.newFixedThreadPool(10).asCoroutineDispatcher()?
answer
- View borrows IO threads, no allocation
- Fixed pool = 10 dedicated threads, must close()
- Idle dedicated threads waste memory
- Custom pool only for isolation/thread customization
- asCoroutineDispatcher -> ExecutorCoroutineDispatcher needs close
basics
~20 sThe limitedParallelism view reuses the shared IO threads and needs no shutdown, so it's cheaper and safer. A custom fixed pool creates 10 dedicated threads you must shut down yourself, and they sit idle when unused.
solid answer
~40 s`Dispatchers.IO.limitedParallelism(10)` returns a view that **borrows** worker threads from the global IO pool and only caps concurrency at 10 — it allocates **no dedicated threads** and needs **no lifecycle management** (no close/shutdown). A `newFixedThreadPool(10).asCoroutineDispatcher()` creates **10 real OS threads** that stay alive (and idle) for the dispatcher's lifetime, must be **closed/shutdown** to avoid leaks, and don't share with the rest of your IO work, so under-utilized you waste threads while over-subscribing the machine. For blocking IO, the IO view also benefits from IO's elastic backing pool. The limited view is the idiomatic, resource-efficient choice; reach for a dedicated pool only when you genuinely need isolation (e.g., a thread pool with special thread-locals, priorities, or to fence off a misbehaving blocking library).
go deeper
Knows the view caps to 10 and the fixed pool makes 10 threads; prefers the simpler view.
Explains no-allocation, no-close, and shared elastic backing as concrete advantages of the view.
Reasons about thread-budget across many subsystems and names legitimate cases for a dedicated pool.
Sets a system-wide policy: one bounded IO pool sliced by views, with dedicated pools only for isolation/SLAs, and reasons about total thread count vs cores.
## The two options ```kotlin // Option A: view over the shared IO pool val a = Dispatchers.IO.limitedParallelism(10) // Option B: dedicated fixed pool turned into a dispatcher val b = Executors.newFixedThreadPool(10).asCoroutineDispatcher() ``` ## Why A is usually better ### 1. No dedicated threads - **A** borrows from the global IO pool's worker threads. It allocates **zero** new threads; it just enforces the cap of 10 concurrent coroutines. - **B** spins up **10 OS threads** that exist for the dispatcher's lifetime. Each thread costs memory (stack ~512KB–1MB) and scheduler overhead, even when idle. ### 2. No lifecycle management - **A** holds nothing to release — there's **no `close()`**. - **B** is an `ExecutorCoroutineDispatcher`; you **must `close()`** it (or shut down the executor) or you leak threads. Easy to forget in tests or short-lived components. ### 3. Shared, elastic backing - IO's pool is shared across all IO work and is elastic up to its soft limit. **A** participates in that sharing; **B** is a static island that can't lend its threads to other work nor borrow more under load. ### 4. Over/under-subscription - Many independent fixed pools (`B` repeated) add up to far more threads than cores, hurting throughput. Views (`A`) all draw from one bounded pool, so total thread count stays controlled. ## When a dedicated pool is justified - You need **isolation** from other IO work (a flaky blocking library you want to fence off). - You need special thread properties: custom names for debugging at scale, thread priorities, or thread-local setup. - You're integrating with a framework that hands you an `ExecutorService`. ## Bottom line Default to `Dispatchers.IO.limitedParallelism(n)` for throttling. Use `asCoroutineDispatcher()` over a custom pool only when isolation or thread customization is a real requirement — and remember to **close** it.
- What's the lifecycle risk of asCoroutineDispatcher() over a custom executor?It produces an ExecutorCoroutineDispatcher that must be closed/shutdown; forgetting leaks the executor's threads.
- When is a dedicated fixed pool actually the right call?When you need isolation from other IO work, custom thread properties (names, priorities, thread-locals), or to integrate an existing ExecutorService.
saying these in an interview costs you the question
- Believing the fixed pool is more efficient because it 'reserves' threads
- Forgetting the fixed-pool dispatcher must be closed
- Creating many fixed pools and over-subscribing CPU
- Thinking limitedParallelism gives weaker concurrency guarantees than a real pool