Shared mutable state

Coroutines Mid-level Kotlin 2.0+ API 24+ Reviewed By Best Latest Apps

Pick the fix

1,000 coroutines each call increment() on Dispatchers.Default. The total is often below 1,000. What's the best fix?

class Counter {
    val state = MutableStateFlow(0)
    fun increment() {
        state.value = state.value + 1
    }
}
Options
Hint 1 of 1

How many steps is value + 1 followed by a write?

Choose an answer, then open Show answer below to check it.

Show answer

Answer: A Use state.update { it + 1 } on the StateFlow

Correct. update retries with compare-and-set, so no increment is lost.

Why not the others?
  • B Mark the property @Volatile. That only affects visibility; read-modify-write is still not atomic.
  • C Run the coroutines with Dispatchers.IO. IO is also multi-threaded; the race stays.
  • D Add yield() before the write. That changes timing but doesn't make the update atomic.

30-second answer

count++ is a read, an add and a write. On a multi-thread dispatcher, two coroutines can read the same value and both write it back, so increments are lost. Fix it by making the update atomic (AtomicInteger, MutableStateFlow.update), by guarding it with a Mutex, or by confining the state to one thread.

Practise saying it · 40 s. No recording, just a timer

Explain how to protect shared state in coroutines.

Say your answer out loud, then check which of these points you covered:

  • count++ is not atomic
  • Atomic update or StateFlow.update
  • Mutex suspends instead of blocking
  • Or confine to one thread

Deep dive

Mutex suspends instead of blocking, so it's safe in coroutines; synchronized blocks a thread and can't contain suspension points.

MutableStateFlow.update { } retries with compare-and-set, which is the idiomatic way to change UI state.

Confinement: run all access on Dispatchers.Main or a limitedParallelism(1) view.

@Volatile makes writes visible but does not make ++ atomic.

Common mistakes

  • Using @Volatile and calling it thread-safe.
  • Holding a Mutex across a slow network call.
  • Writing state.value = state.value.copy(...) from several coroutines.

Senior take & follow-ups

Name all three fixes and when each fits: atomic update for one value, Mutex for a multi-step section, confinement for a whole object.

Follow-up questions

Think of your answer first, then open the hint.

  1. Why not synchronized around a suspend call?

    Show hint

    It won't compile: a suspension point is not allowed inside a critical section.

  2. Is Mutex reentrant?

    Show hint

    No, locking it again from the same coroutine deadlocks.

Similar on iOS: What is an actor? Swift uses actors for this; Kotlin has no actor type in common use, so you choose Mutex, atomics or confinement.

Suggest a fix if something here is wrong or out of date.