You can pass a coroutine context to launch, async, etc. The context holds a dispatcher which decides where to schedule coroutines.
The default context is the CommonPool which holds CPU-1 threads (or something like that).
If you have blocking code in your coroutine, that will tie up one thread until the blocking code terminates. If all threads are tied up, new coroutines will not execute until one of the threads is released due to a coroutine suspending out terminating.
You can create contexts with your own dispatchers with methods like newFixedThreadPoolContext.