Solution: UserDownloader

This is the solution using a dispatcher limited to a single thread and a read-only list:

class UserDownloader(private val api: NetworkService) { private var users = listOf<User>() private val dispatcher = Dispatchers.IO .limitedParallelism(1) fun downloaded(): List<User> = users suspend fun getUser(id: Int) = withContext(dispatcher) { val newUser = api.getUser(id) users += newUser } }

This is the solution using a dispatcher limited to a single thread and a mutable list:

class UserDownloader(private val api: NetworkService) { private val users = mutableListOf<User>() private val dispatcher = Dispatchers.IO .limitedParallelism(1) suspend fun downloaded(): List<User>=withContext(dispatcher){ users.toList() } suspend fun getUser(id: Int) = withContext(dispatcher) { val newUser = api.getUser(id) users += newUser } }

This is the solution using a synchronized block and a mutable list:

class UserDownloader(private val api: NetworkService) { private val users = mutableListOf<User>() private val lock = Any() suspend fun downloaded(): List<User> = synchronized(lock) { users.toList() } suspend fun getUser(id: Int) { val newUser = api.getUser(id) synchronized(lock) { users += newUser } } }

This is the solution using a concurrent set:

class UserDownloader(private val api: NetworkService) { private val users = ConcurrentHashMap.newKeySet<User>() fun downloaded(): List<User> = users.toList() suspend fun getUser(id: Int) { val newUser = api.getUser(id) users += newUser } }

Example solution in playground

import kotlinx.coroutines.* import org.junit.Test import kotlin.test.assertEquals data class User(val name: String) interface NetworkService { suspend fun getUser(id: Int): User } class FakeNetworkService : NetworkService { override suspend fun getUser(id: Int): User { delay(2) return User("User$id") } } class UserDownloader(private val api: NetworkService) { private var users = listOf<User>() private val dispatcher = Dispatchers.IO .limitedParallelism(1) fun downloaded(): List<User> = users suspend fun getUser(id: Int) = withContext(dispatcher) { val newUser = api.getUser(id) users += newUser } } suspend fun main(): Unit = coroutineScope { val downloader = UserDownloader(FakeNetworkService()) coroutineScope { repeat(1_000_000) { launch { downloader.getUser(it) } } } print(downloader.downloaded().size) // ~714725 } class UserDownloaderTest { @Test fun test() = runBlocking { val downloader = UserDownloader(FakeNetworkService()) coroutineScope { repeat(1_000_000) { launch(Dispatchers.Default) { downloader.getUser(it) } } } assertEquals(1_000_000, downloader.downloaded().size) } }