When updating a MutableStateFlow, why prefer update { } / compareAndSet over reading and reassigning .value? Show a correct pattern.
answer
- Read-then-write = lost-update race
- compareAndSet is the CAS primitive
- update { } loops on CAS, atomic
- Lambda must be side-effect-free (may retry)
- Plain .value = x fine when not derived from old
basics
~10 sReading .value then writing it back can lose updates when two threads do it at once. update { } applies your change atomically, so concurrent updates don't clobber each other.
solid answer
~40 sDirect read-modify-write on .value (`s.value = s.value + 1`) is not atomic: between reading and writing, another coroutine/thread can change the value, and one update gets lost (lost-update race). MutableStateFlow exposes atomic helpers: `compareAndSet(expect, update)` sets the value only if it still equals expect, returning whether it succeeded; and `update { current -> newValue }`, which loops on compareAndSet until it wins, so it's safe under concurrency. There's also `getAndUpdate` / `updateAndGet`. Use `update { }` for read-modify-write on shared state, especially when state is a data class you copy(). Plain `.value = x` is fine when the new value doesn't depend on the old one. The function passed to update must be side-effect-free since it can be retried.
code
kotlin · 5 linesval state = MutableStateFlow(UiState())
// atomic, race-safe read-modify-write
state.update { it.copy(count = it.count + 1) }
// vs unsafe:
// state.value = state.value.copy(count = state.value.count + 1)go deeper
Recognizes you can set .value but may not see the concurrency hazard.
Explains the lost-update race and uses update { } / compareAndSet for atomic read-modify-write.
Knows update retries via CAS so the lambda must be pure, and applies copy() on data-class state correctly.
Reasons about contention and atomicity trade-offs and when a single source of truth with atomic updates is the right concurrency model.
## The hazard: lost updates `MutableStateFlow.value` is a simple read or write. A read-modify-write done in two steps is **not atomic**: ```kotlin // UNSAFE under concurrency state.value = state.value + 1 ``` If two coroutines on different threads both read `5`, both compute `6`, and both write `6`, one increment is lost. ## Atomic primitives on MutableStateFlow - `compareAndSet(expect, update): Boolean` — atomically sets the value to `update` **only if** the current value still equals `expect`; returns `true` on success. This is the building block (CAS = compare-and-set). - `update { current -> newValue }` — reads the current value, computes a new one, and loops on `compareAndSet` until it succeeds. Safe under concurrency. - `getAndUpdate { }` / `updateAndGet { }` — same loop, returning the old or new value respectively. ```kotlin val state = MutableStateFlow(0) state.update { it + 1 } // atomic increment ``` ## With data-class state The common UI pattern is copy-on-write of an immutable state object: ```kotlin data class UiState(val count: Int = 0, val loading: Boolean = false) val state = MutableStateFlow(UiState()) state.update { current -> current.copy(count = current.count + 1) } ``` ## Important: the lambda may run more than once Because `update` retries via CAS when it loses a race, **the lambda must be pure** — no side effects (logging counters, network calls, mutation) inside it, since it can be invoked multiple times. ## When plain assignment is fine If the new value does **not** depend on the old value (e.g. `state.value = freshUiState`), a direct `.value = x` is fine — there's no read-modify-write to race. Use `update { }` specifically when the new value is derived from the current one.
- Why must the lambda passed to update { } avoid side effects?On a CAS conflict update retries the lambda, so side effects could run multiple times; it should only compute the new value purely.
- Is `state.value = newComputedState` ever acceptable?Yes, when the new value doesn't depend on the current value, so there's no read-modify-write race to lose.
saying these in an interview costs you the question
- Insisting .value = .value + 1 is always safe
- Not knowing compareAndSet / update exist
- Putting side effects inside the update lambda
- Confusing update { } with a synchronized block (it's CAS-based)
- Thinking a single .value write is non-atomic